Add support for deep linking through push notification data
This commit is contained in:
parent
10c0e531b7
commit
aec0d7c5df
@ -222,8 +222,11 @@ int Thing::executeAction(const QString &actionName, const QVariantList ¶ms)
|
|||||||
{
|
{
|
||||||
ActionType *actionType = m_thingClass->actionTypes()->findByName(actionName);
|
ActionType *actionType = m_thingClass->actionTypes()->findByName(actionName);
|
||||||
if (!actionType) {
|
if (!actionType) {
|
||||||
qCWarning(dcThingManager) << "No such action name" << actionName << "in thing class" << m_thingClass->name();
|
actionType = m_thingClass->actionTypes()->getActionType(QUuid(actionName));
|
||||||
return -1;
|
if (!actionType) {
|
||||||
|
qCWarning(dcThingManager) << "No such action" << actionName << "in thing class" << m_thingClass->name();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariantList finalParams;
|
QVariantList finalParams;
|
||||||
|
|||||||
@ -105,7 +105,8 @@ ubuntu_files.files += \
|
|||||||
packaging/ubuntu/click/appicon.svg \
|
packaging/ubuntu/click/appicon.svg \
|
||||||
packaging/ubuntu/click/push.json \
|
packaging/ubuntu/click/push.json \
|
||||||
packaging/ubuntu/click/push-apparmor.json \
|
packaging/ubuntu/click/push-apparmor.json \
|
||||||
packaging/ubuntu/click/pushexec
|
packaging/ubuntu/click/pushexec \
|
||||||
|
packaging/ubuntu/click/urls.json
|
||||||
|
|
||||||
INSTALLS += ubuntu_files
|
INSTALLS += ubuntu_files
|
||||||
}
|
}
|
||||||
|
|||||||
@ -98,7 +98,13 @@ int main(int argc, char *argv[])
|
|||||||
// Initialize app log controller as early as possible, but after setting app name and printing initial startup info
|
// Initialize app log controller as early as possible, but after setting app name and printing initial startup info
|
||||||
AppLogController::instance();
|
AppLogController::instance();
|
||||||
|
|
||||||
qCInfo(dcApplication()) << "*** nymea:app starting ***" << QDateTime::currentDateTime().toString();
|
qCInfo(dcApplication()) << "*** nymea:app starting ***" << QDateTime::currentDateTime().toString() << application.arguments();
|
||||||
|
|
||||||
|
foreach (const QString &argument, application.arguments()) {
|
||||||
|
if (argument.startsWith("nymea://notification")) {
|
||||||
|
PlatformHelper::instance()->notificationActionReceived(QUrlQuery(QUrl(argument).query()).queryItemValue("nymeaData"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QTranslator qtTranslator;
|
QTranslator qtTranslator;
|
||||||
qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
|
qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
|
||||||
|
|||||||
@ -183,7 +183,7 @@ ubports: {
|
|||||||
DEFINES += UBPORTS
|
DEFINES += UBPORTS
|
||||||
|
|
||||||
CONFIG += link_pkgconfig
|
CONFIG += link_pkgconfig
|
||||||
PKGCONFIG += connectivity-qt1
|
PKGCONFIG += connectivity-qt1 dbus-1 libnih-dbus libnih
|
||||||
|
|
||||||
HEADERS += platformintegration/ubports/pushclient.h \
|
HEADERS += platformintegration/ubports/pushclient.h \
|
||||||
platformintegration/ubports/platformhelperubports.h \
|
platformintegration/ubports/platformhelperubports.h \
|
||||||
|
|||||||
@ -34,6 +34,8 @@
|
|||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
#include <QDesktopServices>
|
#include <QDesktopServices>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
#include <QUrlQuery>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
|
||||||
#if defined Q_OS_ANDROID
|
#if defined Q_OS_ANDROID
|
||||||
#include <QtAndroidExtras/QtAndroid>
|
#include <QtAndroidExtras/QtAndroid>
|
||||||
@ -56,9 +58,34 @@ PlatformHelper::PlatformHelper(QObject *parent) : QObject(parent)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PlatformHelper *PlatformHelper::instance()
|
void PlatformHelper::notificationActionReceived(const QString &nymeaData)
|
||||||
{
|
{
|
||||||
if (!s_instance) {
|
QJsonParseError error;
|
||||||
|
QJsonDocument jsonDoc = QJsonDocument::fromJson(nymeaData.toUtf8(), &error);
|
||||||
|
if (error.error != QJsonParseError::NoError) {
|
||||||
|
qCWarning(dcPlatformIntegration()) << "Received a notification action but cannot parse it:" << error.errorString() << nymeaData;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QVariantMap map = jsonDoc.toVariant().toMap();
|
||||||
|
QUuid id = QUuid::createUuid();
|
||||||
|
map.insert("id", id);
|
||||||
|
|
||||||
|
// transforming data from a url query to a map for easier processing in QML
|
||||||
|
QUrlQuery query(map.value("data").toString());
|
||||||
|
QVariantMap dataMap;
|
||||||
|
for (int i = 0; i < query.queryItems().count(); i++) {
|
||||||
|
const QPair<QString, QString> &item = query.queryItems().at(i);
|
||||||
|
dataMap.insert(item.first, item.second);
|
||||||
|
}
|
||||||
|
map.insert("dataMap", dataMap);
|
||||||
|
|
||||||
|
m_pendingNotificationActions.insert(id, map);
|
||||||
|
emit pendingNotificationActionsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
PlatformHelper *PlatformHelper::instance(bool create)
|
||||||
|
{
|
||||||
|
if (!s_instance && create) {
|
||||||
#ifdef Q_OS_ANDROID
|
#ifdef Q_OS_ANDROID
|
||||||
s_instance = new PlatformHelperAndroid();
|
s_instance = new PlatformHelperAndroid();
|
||||||
#elif defined(Q_OS_IOS)
|
#elif defined(Q_OS_IOS)
|
||||||
@ -178,6 +205,17 @@ bool PlatformHelper::darkModeEnabled() const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QVariantList PlatformHelper::pendingNotificationActions() const
|
||||||
|
{
|
||||||
|
return m_pendingNotificationActions.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlatformHelper::notificationActionHandled(const QUuid &id)
|
||||||
|
{
|
||||||
|
m_pendingNotificationActions.remove(id);
|
||||||
|
emit pendingNotificationActionsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
bool PlatformHelper::splashVisible() const
|
bool PlatformHelper::splashVisible() const
|
||||||
{
|
{
|
||||||
return m_splashVisible;
|
return m_splashVisible;
|
||||||
|
|||||||
@ -33,6 +33,9 @@
|
|||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
|
#include <QHash>
|
||||||
|
#include <QUuid>
|
||||||
|
#include <QVariant>
|
||||||
|
|
||||||
class QQmlEngine;
|
class QQmlEngine;
|
||||||
class QJSEngine;
|
class QJSEngine;
|
||||||
@ -54,6 +57,7 @@ class PlatformHelper : public QObject
|
|||||||
Q_PROPERTY(QColor topPanelColor READ topPanelColor WRITE setTopPanelColor NOTIFY topPanelColorChanged)
|
Q_PROPERTY(QColor topPanelColor READ topPanelColor WRITE setTopPanelColor NOTIFY topPanelColorChanged)
|
||||||
Q_PROPERTY(QColor bottomPanelColor READ bottomPanelColor WRITE setBottomPanelColor NOTIFY bottomPanelColorChanged)
|
Q_PROPERTY(QColor bottomPanelColor READ bottomPanelColor WRITE setBottomPanelColor NOTIFY bottomPanelColorChanged)
|
||||||
Q_PROPERTY(bool darkModeEnabled READ darkModeEnabled NOTIFY darkModeEnabledChanged)
|
Q_PROPERTY(bool darkModeEnabled READ darkModeEnabled NOTIFY darkModeEnabledChanged)
|
||||||
|
Q_PROPERTY(QVariantList pendingNotificationActions READ pendingNotificationActions NOTIFY pendingNotificationActionsChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum HapticsFeedback {
|
enum HapticsFeedback {
|
||||||
@ -63,7 +67,7 @@ public:
|
|||||||
};
|
};
|
||||||
Q_ENUM(HapticsFeedback)
|
Q_ENUM(HapticsFeedback)
|
||||||
|
|
||||||
static PlatformHelper* instance();
|
static PlatformHelper* instance(bool create = true);
|
||||||
virtual ~PlatformHelper() = default;
|
virtual ~PlatformHelper() = default;
|
||||||
|
|
||||||
virtual bool hasPermissions() const;
|
virtual bool hasPermissions() const;
|
||||||
@ -89,6 +93,9 @@ public:
|
|||||||
|
|
||||||
virtual bool darkModeEnabled() const;
|
virtual bool darkModeEnabled() const;
|
||||||
|
|
||||||
|
QVariantList pendingNotificationActions() const;
|
||||||
|
Q_INVOKABLE void notificationActionHandled(const QUuid &id);
|
||||||
|
|
||||||
virtual bool splashVisible() const;
|
virtual bool splashVisible() const;
|
||||||
virtual void setSplashVisible(bool splashVisible);
|
virtual void setSplashVisible(bool splashVisible);
|
||||||
Q_INVOKABLE virtual void hideSplashScreen();
|
Q_INVOKABLE virtual void hideSplashScreen();
|
||||||
@ -102,6 +109,9 @@ public:
|
|||||||
Q_INVOKABLE virtual void shareFile(const QString &fileName);
|
Q_INVOKABLE virtual void shareFile(const QString &fileName);
|
||||||
|
|
||||||
static QObject *platformHelperProvider(QQmlEngine *engine, QJSEngine *scriptEngine);
|
static QObject *platformHelperProvider(QQmlEngine *engine, QJSEngine *scriptEngine);
|
||||||
|
|
||||||
|
void notificationActionReceived(const QString &nymeaData);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void permissionsRequestFinished();
|
void permissionsRequestFinished();
|
||||||
void screenTimeoutChanged();
|
void screenTimeoutChanged();
|
||||||
@ -110,6 +120,7 @@ signals:
|
|||||||
void bottomPanelColorChanged();
|
void bottomPanelColorChanged();
|
||||||
void darkModeEnabledChanged();
|
void darkModeEnabledChanged();
|
||||||
void splashVisibleChanged();
|
void splashVisibleChanged();
|
||||||
|
void pendingNotificationActionsChanged();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
explicit PlatformHelper(QObject *parent = nullptr);
|
explicit PlatformHelper(QObject *parent = nullptr);
|
||||||
@ -121,6 +132,8 @@ private:
|
|||||||
QColor m_bottomPanelColor = QColor("black");
|
QColor m_bottomPanelColor = QColor("black");
|
||||||
|
|
||||||
bool m_splashVisible = true;
|
bool m_splashVisible = true;
|
||||||
|
|
||||||
|
QHash<QUuid, QVariant> m_pendingNotificationActions;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // PLATFORMHELPER_H
|
#endif // PLATFORMHELPER_H
|
||||||
|
|||||||
@ -44,7 +44,13 @@ public class NymeaAppNotificationService extends FirebaseMessagingService {
|
|||||||
Intent intent = new Intent(this, NymeaAppActivity.class);
|
Intent intent = new Intent(this, NymeaAppActivity.class);
|
||||||
// intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
// intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
// PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent, PendingIntent.FLAG_ONE_SHOT);
|
// PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent, PendingIntent.FLAG_ONE_SHOT);
|
||||||
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent, 0);
|
|
||||||
|
Log.d(TAG, "adding extra data to intent: " + remoteMessage.getData().get("nymeaData"));
|
||||||
|
|
||||||
|
intent.setAction(Intent.ACTION_SEND);
|
||||||
|
intent.putExtra("notificationData", remoteMessage.getData().get("nymeaData"));
|
||||||
|
|
||||||
|
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent, PendingIntent.FLAG_CANCEL_CURRENT);
|
||||||
|
|
||||||
// We can't directly access R.drawable.ic_stat_notification from here:
|
// We can't directly access R.drawable.ic_stat_notification from here:
|
||||||
// When the package is branded, the package name is not "io.guh.nymeaapp" and resources in
|
// When the package is branded, the package name is not "io.guh.nymeaapp" and resources in
|
||||||
@ -63,6 +69,17 @@ public class NymeaAppNotificationService extends FirebaseMessagingService {
|
|||||||
.setSound(android.provider.Settings.System.DEFAULT_RINGTONE_URI)
|
.setSound(android.provider.Settings.System.DEFAULT_RINGTONE_URI)
|
||||||
.setContentIntent(pendingIntent);
|
.setContentIntent(pendingIntent);
|
||||||
|
|
||||||
|
// Action tests
|
||||||
|
// Intent actionIntent = new Intent(this, NymeaAppActivity.class);
|
||||||
|
// actionIntent.setAction(Intent.ACTION_SEND);
|
||||||
|
// actionIntent.putExtra("foobar", "baz");
|
||||||
|
// PendingIntent actionPendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, actionIntent, PendingIntent.FLAG_CANCEL_CURRENT);
|
||||||
|
|
||||||
|
// notificationBuilder.addAction(resId, "30%", actionPendingIntent);
|
||||||
|
// notificationBuilder.addAction(resId, "50%", actionPendingIntent);
|
||||||
|
// notificationBuilder.addAction(resId, "70%", actionPendingIntent);
|
||||||
|
// Action tests end
|
||||||
|
|
||||||
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
|||||||
@ -20,6 +20,7 @@ public class NymeaAppActivity extends org.qtproject.qt5.android.bindings.QtActiv
|
|||||||
private static Context context = null;
|
private static Context context = null;
|
||||||
|
|
||||||
private static native void darkModeEnabledChangedJNI();
|
private static native void darkModeEnabledChangedJNI();
|
||||||
|
private static native void notificationActionReceivedJNI(String data);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
@ -27,12 +28,24 @@ public class NymeaAppActivity extends org.qtproject.qt5.android.bindings.QtActiv
|
|||||||
this.context = getApplicationContext();
|
this.context = getApplicationContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onNewIntent (Intent intent) {
|
||||||
|
Log.d(TAG, "New intent: " + intent);
|
||||||
|
String notificationData = intent.getStringExtra("notificationData");
|
||||||
|
if (notificationData != null) {
|
||||||
|
Log.d(TAG, "Intent data: " + notificationData);
|
||||||
|
notificationActionReceivedJNI(notificationData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConfigurationChanged(Configuration newConfig) {
|
public void onConfigurationChanged(Configuration newConfig) {
|
||||||
super.onConfigurationChanged(newConfig);
|
super.onConfigurationChanged(newConfig);
|
||||||
NymeaAppActivity.darkModeEnabledChangedJNI();
|
NymeaAppActivity.darkModeEnabledChangedJNI();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String notificationData() {
|
||||||
|
return getIntent().getStringExtra("notificationData");
|
||||||
|
}
|
||||||
|
|
||||||
public static Context getAppContext() {
|
public static Context getAppContext() {
|
||||||
return NymeaAppActivity.context;
|
return NymeaAppActivity.context;
|
||||||
|
|||||||
@ -48,6 +48,7 @@ static PlatformHelperAndroid *m_instance = nullptr;
|
|||||||
|
|
||||||
static JNINativeMethod methods[] = {
|
static JNINativeMethod methods[] = {
|
||||||
{ "darkModeEnabledChangedJNI", "()V", (void *)PlatformHelperAndroid::darkModeEnabledChangedJNI },
|
{ "darkModeEnabledChangedJNI", "()V", (void *)PlatformHelperAndroid::darkModeEnabledChangedJNI },
|
||||||
|
{ "notificationActionReceivedJNI", "(Ljava/lang/String;)V", (void *)PlatformHelperAndroid::notificationActionReceivedJNI },
|
||||||
};
|
};
|
||||||
|
|
||||||
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/)
|
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/)
|
||||||
@ -77,6 +78,10 @@ PlatformHelperAndroid::PlatformHelperAndroid(QObject *parent) : PlatformHelper(p
|
|||||||
{
|
{
|
||||||
m_instance = this;
|
m_instance = this;
|
||||||
|
|
||||||
|
QString notificationData = QtAndroid::androidActivity().callObjectMethod("notificationData", "()Ljava/lang/String;").toString();
|
||||||
|
if (!notificationData.isNull()) {
|
||||||
|
notificationActionReceived(notificationData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlatformHelperAndroid::requestPermissions()
|
void PlatformHelperAndroid::requestPermissions()
|
||||||
@ -274,3 +279,13 @@ void PlatformHelperAndroid::darkModeEnabledChangedJNI()
|
|||||||
emit m_instance->darkModeEnabledChanged();
|
emit m_instance->darkModeEnabledChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PlatformHelperAndroid::notificationActionReceivedJNI(JNIEnv *env, jobject, jstring data)
|
||||||
|
{
|
||||||
|
// Only call the platformhelper if it exists yet. We may get this callback before the Qt part is created
|
||||||
|
// and we don't want to create the PlatformHelper on the android thread.
|
||||||
|
PlatformHelper* platformHelper = PlatformHelperAndroid::instance(false);
|
||||||
|
if (platformHelper) {
|
||||||
|
platformHelper->notificationActionReceived(env->GetStringUTFChars(data, nullptr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -68,6 +68,7 @@ public:
|
|||||||
void shareFile(const QString &fileName) override;
|
void shareFile(const QString &fileName) override;
|
||||||
|
|
||||||
static void darkModeEnabledChangedJNI();
|
static void darkModeEnabledChangedJNI();
|
||||||
|
static void notificationActionReceivedJNI(JNIEnv *env, jobject /*thiz*/, jstring data);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void permissionRequestFinished(const QtAndroid::PermissionResultMap &);
|
static void permissionRequestFinished(const QtAndroid::PermissionResultMap &);
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
#import "UIKit/UIKit.h"
|
#import "UIKit/UIKit.h"
|
||||||
#import <UserNotifications/UserNotifications.h>
|
#import <UserNotifications/UserNotifications.h>
|
||||||
|
|
||||||
// Include our C++ class
|
|
||||||
#include "pushnotifications.h"
|
#include "pushnotifications.h"
|
||||||
|
#include "platformhelper.h"
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
|
||||||
@ -76,6 +76,11 @@
|
|||||||
-(void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler{
|
-(void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler{
|
||||||
NSLog(@"User Info : %@",response.notification.request.content.userInfo);
|
NSLog(@"User Info : %@",response.notification.request.content.userInfo);
|
||||||
qDebug() << "received notification response!";
|
qDebug() << "received notification response!";
|
||||||
|
|
||||||
|
NSString *nymeaData = response.notification.request.content.userInfo[@"gcm.notification.nymeaData"];
|
||||||
|
|
||||||
|
PlatformHelper::instance()->notificationActionReceived(QString::fromNSString(nymeaData));
|
||||||
|
|
||||||
completionHandler();
|
completionHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,23 @@
|
|||||||
|
#include <libnih.h>
|
||||||
|
#include <libnih-dbus.h>
|
||||||
|
|
||||||
#include "platformhelperubports.h"
|
#include "platformhelperubports.h"
|
||||||
|
|
||||||
|
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QUuid>
|
#include <QUuid>
|
||||||
|
#include <QUrl>
|
||||||
|
#include <QUrlQuery>
|
||||||
|
#include <QDBusConnection>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
|
||||||
PlatformHelperUBPorts::PlatformHelperUBPorts(QObject *parent) : PlatformHelper(parent)
|
|
||||||
|
PlatformHelperUBPorts::PlatformHelperUBPorts(QObject *parent):
|
||||||
|
PlatformHelper(parent),
|
||||||
|
m_uriHandlerObject(this)
|
||||||
{
|
{
|
||||||
|
setupUriHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString PlatformHelperUBPorts::platform() const
|
QString PlatformHelperUBPorts::platform() const
|
||||||
@ -21,3 +33,49 @@ QString PlatformHelperUBPorts::deviceSerial() const
|
|||||||
}
|
}
|
||||||
return s.value("deviceSerial").toString();
|
return s.value("deviceSerial").toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PlatformHelperUBPorts::setupUriHandler()
|
||||||
|
{
|
||||||
|
QString objectPath;
|
||||||
|
|
||||||
|
if (!QDBusConnection::sessionBus().isConnected()) {
|
||||||
|
qWarning() << "UCUriHandler: D-Bus session bus is not connected, ignoring.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the object path based on the "APP_ID" environment variable.
|
||||||
|
QByteArray applicationId = qgetenv("APP_ID");
|
||||||
|
if (applicationId.isEmpty()) {
|
||||||
|
qWarning() << "UCUriHandler: Empty \"APP_ID\" environment variable, ignoring.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
char* path = nih_dbus_path(NULL, "", applicationId.constData(), nullptr);
|
||||||
|
objectPath = QString::fromLocal8Bit(path);
|
||||||
|
nih_free(path);
|
||||||
|
|
||||||
|
// Ensure handler is running on the main thread.
|
||||||
|
QCoreApplication* instance = QCoreApplication::instance();
|
||||||
|
if (instance) {
|
||||||
|
moveToThread(instance->thread());
|
||||||
|
} else {
|
||||||
|
qWarning() << "UCUriHandler: Created before QCoreApplication, application may misbehave.";
|
||||||
|
}
|
||||||
|
|
||||||
|
QDBusConnection::sessionBus().registerObject(
|
||||||
|
objectPath, &m_uriHandlerObject, QDBusConnection::ExportAllSlots);
|
||||||
|
}
|
||||||
|
|
||||||
|
UriHandlerObject::UriHandlerObject(PlatformHelper *platformHelper):
|
||||||
|
m_platformHelper(platformHelper)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void UriHandlerObject::Open(const QStringList& uris, const QHash<QString, QVariant>& platformData)
|
||||||
|
{
|
||||||
|
Q_UNUSED(platformData);
|
||||||
|
foreach (const QString &uri, uris) {
|
||||||
|
if (uri.startsWith("nymea://notification")) {
|
||||||
|
m_platformHelper->notificationActionReceived(QUrlQuery(QUrl(uri)).queryItemValue("nymeaData"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -5,6 +5,22 @@
|
|||||||
|
|
||||||
#include "platformhelper.h"
|
#include "platformhelper.h"
|
||||||
|
|
||||||
|
class UriHandlerObject: public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_CLASSINFO("D-Bus Interface", "org.freedesktop.Application")
|
||||||
|
|
||||||
|
public:
|
||||||
|
UriHandlerObject(PlatformHelper* platformHelper);
|
||||||
|
|
||||||
|
public Q_SLOTS:
|
||||||
|
void Open(const QStringList& uris, const QHash<QString, QVariant>& platformData);
|
||||||
|
|
||||||
|
private:
|
||||||
|
PlatformHelper* m_platformHelper = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
class PlatformHelperUBPorts : public PlatformHelper
|
class PlatformHelperUBPorts : public PlatformHelper
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@ -16,6 +32,11 @@ public:
|
|||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setupUriHandler();
|
||||||
|
|
||||||
|
UriHandlerObject m_uriHandlerObject;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // PLATFORMHELPERUBPORTS_H
|
#endif // PLATFORMHELPERUBPORTS_H
|
||||||
|
|||||||
@ -26,6 +26,10 @@
|
|||||||
{
|
{
|
||||||
"name": "body",
|
"name": "body",
|
||||||
"value": "%0 runs out of battery"
|
"value": "%0 runs out of battery"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "data",
|
||||||
|
"value": "open=$0"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -58,6 +62,10 @@
|
|||||||
{
|
{
|
||||||
"name": "body",
|
"name": "body",
|
||||||
"value": "%0 runs dry"
|
"value": "%0 runs dry"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "data",
|
||||||
|
"value": "open=$0"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -90,6 +98,10 @@
|
|||||||
{
|
{
|
||||||
"name": "body",
|
"name": "body",
|
||||||
"value": "%0 has disconnected"
|
"value": "%0 has disconnected"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "data",
|
||||||
|
"value": "open=$0"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -122,6 +134,10 @@
|
|||||||
{
|
{
|
||||||
"name": "body",
|
"name": "body",
|
||||||
"value": "%0 has connected"
|
"value": "%0 has connected"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "data",
|
||||||
|
"value": "open=$0"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -44,6 +44,11 @@ import "mainviews"
|
|||||||
Page {
|
Page {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
// Removing the background from this page only because the MainViewBase adds it again in
|
||||||
|
// a deepter layer as we need to include it in the blurring of the header and footer.
|
||||||
|
// We don't want to paint the background on the entire screen twice (overdraw is costly)
|
||||||
|
background: null
|
||||||
|
|
||||||
function configureViews() {
|
function configureViews() {
|
||||||
if (Configuration.hasOwnProperty("mainViewsFilter")) {
|
if (Configuration.hasOwnProperty("mainViewsFilter")) {
|
||||||
console.warn("Main views configuration is disabled by app configuration")
|
console.warn("Main views configuration is disabled by app configuration")
|
||||||
@ -54,10 +59,21 @@ Page {
|
|||||||
d.configOverlay = configComponent.createObject(contentContainer)
|
d.configOverlay = configComponent.createObject(contentContainer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Removing the background from this page only because the MainViewBase adds it again in
|
function goToView(viewName, data) {
|
||||||
// a deepter layer as we need to include it in the blurring of the header and footer.
|
// We allow separating the target by : and pass more stuff to
|
||||||
// We don't want to paint the background on the entire screen twice (overdraw is costly)
|
console.log("Going to main view", viewName, filteredContentModel.count, data)
|
||||||
background: null
|
for (var i = 0; i < filteredContentModel.count; i++) {
|
||||||
|
console.log("got", i, filteredContentModel.modelData(i, "name"))
|
||||||
|
if (filteredContentModel.modelData(i, "name") === viewName) {
|
||||||
|
console.log("activating", i)
|
||||||
|
// mainViewSettings.currentIndex = i;
|
||||||
|
// tabBar.currentIndex = i;
|
||||||
|
swipeView.setCurrentIndex(i)
|
||||||
|
swipeView.currentItem.item.handleEvent(data)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
header: Item {
|
header: Item {
|
||||||
id: mainHeader
|
id: mainHeader
|
||||||
@ -245,6 +261,7 @@ Page {
|
|||||||
Behavior on opacity { NumberAnimation { duration: 200; easing.type: Easing.InOutQuad } }
|
Behavior on opacity { NumberAnimation { duration: 200; easing.type: Easing.InOutQuad } }
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
|
id: mainViewsRepeater
|
||||||
model: d.configOverlay != null ? null : filteredContentModel
|
model: d.configOverlay != null ? null : filteredContentModel
|
||||||
|
|
||||||
delegate: Loader {
|
delegate: Loader {
|
||||||
|
|||||||
@ -426,12 +426,57 @@ Item {
|
|||||||
target: engine.thingManager
|
target: engine.thingManager
|
||||||
onFetchingDataChanged: {
|
onFetchingDataChanged: {
|
||||||
if (!engine.thingManager.fetchingData) {
|
if (!engine.thingManager.fetchingData) {
|
||||||
|
processPendingPushNotificationActions();
|
||||||
updatePushNotificationThings()
|
updatePushNotificationThings()
|
||||||
}
|
}
|
||||||
PlatformHelper.hideSplashScreen();
|
PlatformHelper.hideSplashScreen();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: PlatformHelper
|
||||||
|
onPendingNotificationActionsChanged: {
|
||||||
|
processPendingPushNotificationActions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function processPendingPushNotificationActions() {
|
||||||
|
print("pending notification actions changed:", PlatformHelper.pendingNotificationActions)
|
||||||
|
if (PlatformHelper.pendingNotificationActions.length > 0) {
|
||||||
|
var notificationAction = PlatformHelper.pendingNotificationActions[0]
|
||||||
|
if (notificationAction.serverUuid.replace(/[{]]/g, "") !== engine.jsonRpcClient.serverUuid.toString().replace(/[{}]/g, "")) {
|
||||||
|
print("notification action for different server")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
print("handling action", JSON.stringify(notificationAction))
|
||||||
|
|
||||||
|
if (notificationAction.dataMap.hasOwnProperty("open")) {
|
||||||
|
// It could be just a thing ID
|
||||||
|
var target = notificationAction.dataMap["open"]
|
||||||
|
var thing = engine.thingManager.things.getThing(target)
|
||||||
|
if (thing) {
|
||||||
|
print("opening thing:", thing.name)
|
||||||
|
pageStack.push("/ui/devicepages/" + NymeaUtils.interfaceListToDevicePage(thing.thingClass.interfaces), {thing: thing})
|
||||||
|
} else {
|
||||||
|
// or a view name
|
||||||
|
console.log("going to main view:", target)
|
||||||
|
pageStack.currentItem.goToView(target, notificationAction.dataMap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notificationAction.dataMap.hasOwnProperty("execute")) {
|
||||||
|
var action = notificationAction.dataMap["execute"]
|
||||||
|
var thingId = notificationAction.dataMap["thingId"]
|
||||||
|
var actionParams = JSON.parse(notificationAction.dataMap["actionParams"])
|
||||||
|
print("executing:", thingId, action, actionParams)
|
||||||
|
engine.thingManager.things.getThing(thingId).executeAction(action, actionParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
PlatformHelper.notificationActionHandled(notificationAction.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: invalidVersionComponent
|
id: invalidVersionComponent
|
||||||
Popup {
|
Popup {
|
||||||
|
|||||||
@ -49,6 +49,11 @@ Item {
|
|||||||
|
|
||||||
property var headerButtons: []
|
property var headerButtons: []
|
||||||
|
|
||||||
|
// Override this to receive events (e.g. from push notification bubbles)
|
||||||
|
function handleEvent(data) {
|
||||||
|
print("handleEvent not implemented in", title)
|
||||||
|
}
|
||||||
|
|
||||||
Background {
|
Background {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
}
|
}
|
||||||
|
|||||||
@ -747,6 +747,8 @@ WizardPageBase {
|
|||||||
|
|
||||||
content: ColumnLayout {
|
content: ColumnLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: Style.margins
|
||||||
|
Layout.rightMargin: Style.margins
|
||||||
Layout.maximumWidth: 500
|
Layout.maximumWidth: 500
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
Layout.preferredHeight: visibleContentHeight
|
Layout.preferredHeight: visibleContentHeight
|
||||||
@ -755,6 +757,7 @@ WizardPageBase {
|
|||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
text: qsTr("You can now go ahead and configure your nymea system.")
|
text: qsTr("You can now go ahead and configure your nymea system.")
|
||||||
visible: wirelessConnectionCompletedPage.host != null
|
visible: wirelessConnectionCompletedPage.host != null
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
}
|
}
|
||||||
BusyIndicator {
|
BusyIndicator {
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
|||||||
@ -278,6 +278,7 @@ Page {
|
|||||||
print("replacing args", typeof actionParam.value)
|
print("replacing args", typeof actionParam.value)
|
||||||
if (typeof actionParam.value === "string") {
|
if (typeof actionParam.value === "string") {
|
||||||
actionParam.value = actionParam.value.replace("%" + selectionId, thingName);
|
actionParam.value = actionParam.value.replace("%" + selectionId, thingName);
|
||||||
|
actionParam.value = actionParam.value.replace("$" + selectionId, selectedThings[selectionId]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -287,6 +288,7 @@ Page {
|
|||||||
var actionParam = action.ruleActionParams.get(k);
|
var actionParam = action.ruleActionParams.get(k);
|
||||||
if (typeof actionParam.value === "string") {
|
if (typeof actionParam.value === "string") {
|
||||||
actionParam.value = actionParam.value.replace("%" + selectionId, thingName);
|
actionParam.value = actionParam.value.replace("%" + selectionId, thingName);
|
||||||
|
actionParam.value = actionParam.value.replace("$" + selectionId, selectedThings[selectionId]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -445,6 +447,11 @@ Page {
|
|||||||
for (var j = 0; j < ruleActionTemplate.ruleActionParamTemplates.count; j++) {
|
for (var j = 0; j < ruleActionTemplate.ruleActionParamTemplates.count; j++) {
|
||||||
var ruleActionParamTemplate = ruleActionTemplate.ruleActionParamTemplates.get(j)
|
var ruleActionParamTemplate = ruleActionTemplate.ruleActionParamTemplates.get(j)
|
||||||
var paramType = actionType.paramTypes.findByName(ruleActionParamTemplate.paramName);
|
var paramType = actionType.paramTypes.findByName(ruleActionParamTemplate.paramName);
|
||||||
|
if (!paramType) {
|
||||||
|
print("Skipping template action param", ruleActionParamTemplate, "as action type does not have this param")
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (ruleActionParamTemplate.value !== undefined) {
|
if (ruleActionParamTemplate.value !== undefined) {
|
||||||
ruleAction.ruleActionParams.setRuleActionParam(paramType.id, ruleActionParamTemplate.value)
|
ruleAction.ruleActionParams.setRuleActionParam(paramType.id, ruleActionParamTemplate.value)
|
||||||
} else if (ruleActionParamTemplate.eventInterface && ruleActionParamTemplate.eventName && ruleActionParamTemplate.eventParamName) {
|
} else if (ruleActionParamTemplate.eventInterface && ruleActionParamTemplate.eventName && ruleActionParamTemplate.eventParamName) {
|
||||||
|
|||||||
@ -11,7 +11,9 @@
|
|||||||
"qml-module-qtcharts",
|
"qml-module-qtcharts",
|
||||||
"qml-module-qt-labs-calendar",
|
"qml-module-qt-labs-calendar",
|
||||||
"libconnectivity-qt1-dev",
|
"libconnectivity-qt1-dev",
|
||||||
"libunity-api-dev"
|
"libunity-api-dev",
|
||||||
|
"libnih-dbus-dev",
|
||||||
|
"libdbus-1-dev"
|
||||||
],
|
],
|
||||||
"install_qml": [
|
"install_qml": [
|
||||||
"/usr/lib/${ARCH_TRIPLET}/qt5/qml/Qt/labs/calendar/"
|
"/usr/lib/${ARCH_TRIPLET}/qt5/qml/Qt/labs/calendar/"
|
||||||
|
|||||||
@ -6,7 +6,8 @@
|
|||||||
"hooks": {
|
"hooks": {
|
||||||
"nymea-app": {
|
"nymea-app": {
|
||||||
"apparmor": "nymea-app.apparmor",
|
"apparmor": "nymea-app.apparmor",
|
||||||
"desktop": "nymea-app.desktop"
|
"desktop": "nymea-app.desktop",
|
||||||
|
"urls": "urls.json"
|
||||||
},
|
},
|
||||||
"push": {
|
"push": {
|
||||||
"apparmor": "push-apparmor.json",
|
"apparmor": "push-apparmor.json",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Name=nymea:app
|
Name=nymea:app
|
||||||
Exec=usr/bin/nymea-app
|
Exec=usr/bin/nymea-app %U
|
||||||
Icon=appicon.svg
|
Icon=appicon.svg
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Type=Application
|
Type=Application
|
||||||
|
|||||||
@ -7,13 +7,25 @@ import json
|
|||||||
f1, f2 = sys.argv[1:3]
|
f1, f2 = sys.argv[1:3]
|
||||||
|
|
||||||
payloadJson = json.load(open(f1))
|
payloadJson = json.load(open(f1))
|
||||||
|
print("<<<< Input: %s" % payloadJson)
|
||||||
|
|
||||||
|
# Set an icon
|
||||||
dir_path = os.path.dirname(os.path.realpath(__file__))
|
dir_path = os.path.dirname(os.path.realpath(__file__))
|
||||||
payloadJson["notification"]["card"]["icon"] = dir_path + "/appicon.svg"
|
payloadJson["notification"]["card"]["icon"] = dir_path + "/appicon.svg"
|
||||||
|
|
||||||
payloadJson["notification"]["card"]["actions"] = ["appid://io.guh.nymeaapp/nymea-app/current-user-version"]
|
|
||||||
|
# Define the on-click action
|
||||||
|
action = "appid://io.guh.nymeaapp/nymea-app/current-user-version" # The default action (just opening the app)
|
||||||
|
if "nymeaData" in payloadJson["notification"]:
|
||||||
|
action = "nymea://notification?nymeaData=%s" % json.dumps(payloadJson["notification"]["nymeaData"])
|
||||||
|
|
||||||
|
payloadJson["notification"]["card"]["actions"] = [action]
|
||||||
#payloadJson["notification"]["emblem-counter"] = {"count": 1, "visible": True }
|
#payloadJson["notification"]["emblem-counter"] = {"count": 1, "visible": True }
|
||||||
|
|
||||||
print(payloadJson)
|
#print("nymeaData: %s" % nymeaData)
|
||||||
|
print("action: %s" % action)
|
||||||
|
|
||||||
|
print(">>>> Output: %s" % payloadJson)
|
||||||
|
|
||||||
open(f2, "w").write(json.dumps(payloadJson) + "\n")
|
open(f2, "w").write(json.dumps(payloadJson) + "\n")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user