paulxstretch/deps/juce/examples/Utilities/PushNotificationsDemo.h

1245 lines
57 KiB
C
Raw Permalink Normal View History

/*
==============================================================================
This file is part of the JUCE examples.
Copyright (c) 2020 - Raw Material Software Limited
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
/*******************************************************************************
The block below describes the properties of this PIP. A PIP is a short snippet
of code that can be read by the Projucer and used to generate a JUCE project.
BEGIN_JUCE_PIP_METADATA
name: PushNotificationsDemo
version: 2.0.0
vendor: JUCE
website: http://juce.com
description: Showcases push notifications features. To run this demo you must enable the
"Push Notifications Capability" option in the Projucer exporter.
dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats,
juce_audio_processors, juce_audio_utils, juce_core,
juce_data_structures, juce_events, juce_graphics,
juce_gui_basics, juce_gui_extra
exporters: xcode_mac, xcode_iphone, androidstudio
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
JUCE_PUSH_NOTIFICATIONS=1
type: Component
mainClass: PushNotificationsDemo
useLocalCopy: 1
END_JUCE_PIP_METADATA
*******************************************************************************/
#pragma once
#include "../Assets/DemoUtilities.h"
/*
To finish the setup of this demo, do the following:
1. Download google_services.json from your Firebase project.
2. Update "Remote Notifications Config File" path in Android exporter (this can be different for debug and release)
to point to that json file.
3. Add image and sound resources by adding the following to "Extra Android Raw Resources" in Projucer:
../../Assets/Notifications/images/ic_stat_name.png
../../Assets/Notifications/images/ic_stat_name2.png
../../Assets/Notifications/images/ic_stat_name3.png
../../Assets/Notifications/images/ic_stat_name4.png
../../Assets/Notifications/images/ic_stat_name5.png
../../Assets/Notifications/images/ic_stat_name6.png
../../Assets/Notifications/images/ic_stat_name7.png
../../Assets/Notifications/images/ic_stat_name8.png
../../Assets/Notifications/images/ic_stat_name9.png
../../Assets/Notifications/images/ic_stat_name10.png
../../Assets/Notifications/sounds/demonstrative.mp3
../../Assets/Notifications/sounds/isntit.mp3
../../Assets/Notifications/sounds/jinglebellssms.mp3
../../Assets/Notifications/sounds/served.mp3
../../Assets/Notifications/sounds/solemn.mp3
4. Set "Remote Notifications" to enabled in Projucer Android exporter.
To verify that remote notifications are configured properly, go to Remote tab in the demo and press "GetDeviceToken"
button, a dialog with your token (also printed to console in debug build) should show up.
The following steps are only necessary if you have a custom activity defined:
5. Ensure that its launchMode is set to "singleTop" or "singleTask" in Android manifest. This is the default behaviour
in JUCE so you only need to do it if you have custom Android manifest content. You can do it from Projucer by
ensuring that "Custom Manifest XML Content" contains:
<manifest>
<application>
<activity android:launchMode="singleTask">
</activity>
</application>
</manifest>
6. Ensure that you override onNewIntent() function in the same way as it is done in JuceActivity.java:
package com.rmsl.juce;
import android.app.Activity;
import android.content.Intent;
//==============================================================================
public class JuceActivity extends Activity
{
//==============================================================================
private native void appNewIntent (Intent intent);
@Override
protected void onNewIntent (Intent intent)
{
super.onNewIntent(intent);
setIntent(intent);
appNewIntent (intent);
}
}
*/
//==============================================================================
class PushNotificationsDemo : public Component,
private ChangeListener,
private ComponentListener,
private PushNotifications::Listener
{
public:
//==============================================================================
PushNotificationsDemo()
{
setupControls();
distributeControls();
#if JUCE_PUSH_NOTIFICATIONS
addAndMakeVisible (headerLabel);
addAndMakeVisible (mainTabs);
addAndMakeVisible (sendButton);
#else
addAndMakeVisible (notAvailableYetLabel);
#endif
headerLabel .setJustificationType (Justification::centred);
notAvailableYetLabel.setJustificationType (Justification::centred);
#if JUCE_MAC
StringArray tabNames { "Params1", "Params2", "Params3", "Params4" };
#else
StringArray tabNames { "Req. params", "Opt. params1", "Opt. params2", "Opt. params3" };
#endif
auto colour = getLookAndFeel().findColour (ResizableWindow::backgroundColourId);
localNotificationsTabs.addTab (tabNames[0], colour, &paramsOneView, false);
localNotificationsTabs.addTab (tabNames[1], colour, &paramsTwoView, false);
#if JUCE_ANDROID
localNotificationsTabs.addTab (tabNames[2], colour, &paramsThreeView, false);
localNotificationsTabs.addTab (tabNames[3], colour, &paramsFourView, false);
#endif
localNotificationsTabs.addTab ("Aux. actions", colour, &auxActionsView, false);
mainTabs.addTab ("Local", colour, &localNotificationsTabs, false);
mainTabs.addTab ("Remote", colour, &remoteView, false);
auto userArea = Desktop::getInstance().getDisplays().getPrimaryDisplay()->userArea;
#if JUCE_ANDROID || JUCE_IOS
setSize (userArea.getWidth(), userArea.getHeight());
#else
setSize (userArea.getWidth() / 2, userArea.getHeight() / 2);
#endif
sendButton.onClick = [this] { sendLocalNotification(); };
auxActionsView.getDeliveredNotificationsButton .onClick = []
{ PushNotifications::getInstance()->getDeliveredNotifications(); };
auxActionsView.removeDeliveredNotifWithIdButton.onClick = [this]
{ PushNotifications::getInstance()->removeDeliveredNotification (auxActionsView.deliveredNotifIdentifier.getText()); };
auxActionsView.removeAllDeliveredNotifsButton .onClick = []
{ PushNotifications::getInstance()->removeAllDeliveredNotifications(); };
#if JUCE_IOS || JUCE_MAC
auxActionsView.getPendingNotificationsButton .onClick = []
{ PushNotifications::getInstance()->getPendingLocalNotifications(); };
auxActionsView.removePendingNotifWithIdButton.onClick = [this]
{ PushNotifications::getInstance()->removePendingLocalNotification (auxActionsView.pendingNotifIdentifier.getText()); };
auxActionsView.removeAllPendingNotifsButton .onClick = []
{ PushNotifications::getInstance()->removeAllPendingLocalNotifications(); };
#endif
remoteView.getDeviceTokenButton.onClick = []
{
String token = PushNotifications::getInstance()->getDeviceToken();
DBG ("token = " + token);
if (token.isEmpty())
showRemoteInstructions();
else
NativeMessageBox::showAsync (MessageBoxOptions()
.withIconType (MessageBoxIconType::InfoIcon)
.withTitle ("Device token")
.withMessage (token),
nullptr);
};
#if JUCE_ANDROID
remoteView.sendRemoteMessageButton.onClick = []
{
StringPairArray data;
data.set ("key1", "value1");
data.set ("key2", "value2");
static int id = 100;
PushNotifications::getInstance()->sendUpstreamMessage ("872047750958",
"com.juce.pushnotificationsdemo",
String (id++),
"standardType",
3600,
data);
};
remoteView.subscribeToSportsButton .onClick = []
{ PushNotifications::getInstance()->subscribeToTopic ("sports"); };
remoteView.unsubscribeFromSportsButton.onClick = []
{ PushNotifications::getInstance()->unsubscribeFromTopic ("sports"); };
#endif
paramControls.accentColourButton.onClick = [this] { setupAccentColour(); };
paramControls.ledColourButton .onClick = [this] { setupLedColour(); };
jassert (PushNotifications::getInstance()->areNotificationsEnabled());
PushNotifications::getInstance()->addListener (this);
#if JUCE_IOS || JUCE_MAC
paramControls.fireInComboBox.onChange = [this] { delayNotification(); };
PushNotifications::getInstance()->requestPermissionsWithSettings (getNotificationSettings());
#elif JUCE_ANDROID
PushNotifications::ChannelGroup cg { "demoGroup", "demo group" };
PushNotifications::getInstance()->setupChannels ({ { cg } }, getAndroidChannels());
#endif
#if JUCE_IOS || JUCE_ANDROID
setPortraitOrientationEnabled (true);
#endif
}
~PushNotificationsDemo() override
{
PushNotifications::getInstance()->removeListener (this);
#if JUCE_IOS || JUCE_ANDROID
setPortraitOrientationEnabled (false);
#endif
}
void setPortraitOrientationEnabled (bool shouldBeEnabled)
{
auto allowedOrientations = Desktop::getInstance().getOrientationsEnabled();
if (shouldBeEnabled)
allowedOrientations |= Desktop::upright;
else
allowedOrientations &= ~Desktop::upright;
Desktop::getInstance().setOrientationsEnabled (allowedOrientations);
}
void paint (Graphics& g) override
{
g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
}
void resized() override
{
auto bounds = getLocalBounds().reduced (getWidth() / 20, getHeight() / 40);
headerLabel.setBounds (bounds.removeFromTop (bounds.proportionOfHeight (0.1f)));
mainTabs .setBounds (bounds.removeFromTop (bounds.proportionOfHeight (0.8f)));
sendButton .setBounds (bounds);
notAvailableYetLabel.setBounds (getLocalBounds());
}
private:
void delayNotification()
{
auto repeatsAllowed = paramControls.fireInComboBox.getSelectedItemIndex() >= 6;
paramControls.repeatButton.setEnabled (repeatsAllowed);
if (! repeatsAllowed)
paramControls.repeatButton.setToggleState (false, NotificationType::sendNotification);
}
void sendLocalNotification()
{
PushNotifications::Notification n;
fillRequiredParams (n);
fillOptionalParamsOne (n);
#if JUCE_ANDROID
fillOptionalParamsTwo (n);
fillOptionalParamsThree (n);
#endif
if (! n.isValid())
{
#if JUCE_IOS
String requiredFields = "identifier (from iOS 10), title, body and category";
#elif JUCE_ANDROID
String requiredFields = "channel ID (from Android O), title, body and icon";
#else
String requiredFields = "all required fields";
#endif
NativeMessageBox::showAsync (MessageBoxOptions()
.withIconType (MessageBoxIconType::InfoIcon)
.withTitle ("Incorrect notifications setup")
.withMessage ("Please make sure that " + requiredFields + " are set."),
nullptr);
return;
}
PushNotifications::getInstance()->sendLocalNotification (n);
}
void fillRequiredParams (PushNotifications::Notification& n)
{
n.identifier = paramControls.identifierEditor.getText();
n.title = paramControls.titleEditor .getText();
n.body = paramControls.bodyEditor .getText();
#if JUCE_IOS
n.category = paramControls.categoryComboBox.getText();
#elif JUCE_ANDROID || JUCE_MAC
#if JUCE_MAC
String prefix = "Notifications/images/";
String extension = ".png";
#else
String prefix;
String extension;
#endif
if (paramControls.iconComboBox.getSelectedItemIndex() == 0)
n.icon = prefix + "ic_stat_name" + extension;
else if (paramControls.iconComboBox.getSelectedItemIndex() == 1)
n.icon = prefix + "ic_stat_name2" + extension;
else if (paramControls.iconComboBox.getSelectedItemIndex() == 2)
n.icon = prefix + "ic_stat_name3" + extension;
else if (paramControls.iconComboBox.getSelectedItemIndex() == 3)
n.icon = prefix + "ic_stat_name4" + extension;
else if (paramControls.iconComboBox.getSelectedItemIndex() == 4)
n.icon = prefix + "ic_stat_name5" + extension;
#endif
#if JUCE_ANDROID
// Note: this is not strictly speaking required param, just doing it here because it is the fastest way!
n.publicVersion.reset (new PushNotifications::Notification());
n.publicVersion->identifier = "blahblahblah";
n.publicVersion->title = "Public title!";
n.publicVersion->body = "Public body!";
n.publicVersion->icon = n.icon;
n.channelId = String (paramControls.channelIdComboBox.getSelectedItemIndex() + 1);
#endif
}
void fillOptionalParamsOne (PushNotifications::Notification& n)
{
n.subtitle = paramControls.subtitleEditor.getText();
n.badgeNumber = paramControls.badgeNumberComboBox.getSelectedItemIndex();
if (paramControls.soundToPlayComboBox.getSelectedItemIndex() > 0)
n.soundToPlay = URL (paramControls.soundToPlayComboBox.getItemText (paramControls.soundToPlayComboBox.getSelectedItemIndex()));
n.properties = JSON::parse (paramControls.propertiesEditor.getText());
#if JUCE_IOS || JUCE_MAC
n.triggerIntervalSec = double (paramControls.fireInComboBox.getSelectedItemIndex() * 10);
n.repeat = paramControls.repeatButton.getToggleState();
#elif JUCE_ANDROID
if (paramControls.largeIconComboBox.getSelectedItemIndex() == 1)
n.largeIcon = getImageFromAssets ("Notifications/images/ic_stat_name6.png");
else if (paramControls.largeIconComboBox.getSelectedItemIndex() == 2)
n.largeIcon = getImageFromAssets ("Notifications/images/ic_stat_name7.png");
else if (paramControls.largeIconComboBox.getSelectedItemIndex() == 3)
n.largeIcon = getImageFromAssets ("Notifications/images/ic_stat_name8.png");
else if (paramControls.largeIconComboBox.getSelectedItemIndex() == 4)
n.largeIcon = getImageFromAssets ("Notifications/images/ic_stat_name9.png");
else if (paramControls.largeIconComboBox.getSelectedItemIndex() == 5)
n.largeIcon = getImageFromAssets ("Notifications/images/ic_stat_name10.png");
n.badgeIconType = (PushNotifications::Notification::BadgeIconType) paramControls.badgeIconComboBox.getSelectedItemIndex();
n.tickerText = paramControls.tickerTextEditor.getText();
n.shouldAutoCancel = paramControls.autoCancelButton .getToggleState();
n.alertOnlyOnce = paramControls.alertOnlyOnceButton.getToggleState();
#endif
#if JUCE_ANDROID || JUCE_MAC
if (paramControls.actionsComboBox.getSelectedItemIndex() == 1)
{
PushNotifications::Notification::Action a, a2;
a .style = PushNotifications::Notification::Action::button;
a2.style = PushNotifications::Notification::Action::button;
a .title = a .identifier = "Ok";
a2.title = a2.identifier = "Cancel";
n.actions.add (a);
n.actions.add (a2);
}
else if (paramControls.actionsComboBox.getSelectedItemIndex() == 2)
{
PushNotifications::Notification::Action a, a2;
a .title = a .identifier = "Input Text Here";
a2.title = a2.identifier = "No";
a .style = PushNotifications::Notification::Action::text;
a2.style = PushNotifications::Notification::Action::button;
a .icon = "ic_stat_name4";
a2.icon = "ic_stat_name5";
a.textInputPlaceholder = "placeholder text ...";
n.actions.add (a);
n.actions.add (a2);
}
else if (paramControls.actionsComboBox.getSelectedItemIndex() == 3)
{
PushNotifications::Notification::Action a, a2;
a .title = a .identifier = "Ok";
a2.title = a2.identifier = "Cancel";
a .style = PushNotifications::Notification::Action::button;
a2.style = PushNotifications::Notification::Action::button;
a .icon = "ic_stat_name4";
a2.icon = "ic_stat_name5";
n.actions.add (a);
n.actions.add (a2);
}
else if (paramControls.actionsComboBox.getSelectedItemIndex() == 4)
{
PushNotifications::Notification::Action a, a2;
a .title = a .identifier = "Input Text Here";
a2.title = a2.identifier = "No";
a .style = PushNotifications::Notification::Action::text;
a2.style = PushNotifications::Notification::Action::button;
a .icon = "ic_stat_name4";
a2.icon = "ic_stat_name5";
a.textInputPlaceholder = "placeholder text ...";
a.allowedResponses.add ("Response 1");
a.allowedResponses.add ("Response 2");
a.allowedResponses.add ("Response 3");
n.actions.add (a);
n.actions.add (a2);
}
#endif
}
void fillOptionalParamsTwo (PushNotifications::Notification& n)
{
using Notification = PushNotifications::Notification;
Notification::Progress progress;
progress.max = paramControls.progressMaxComboBox .getSelectedItemIndex() * 10;
progress.current = paramControls.progressCurrentComboBox.getSelectedItemIndex() * 10;
progress.indeterminate = paramControls.progressIndeterminateButton.getToggleState();
n.progress = progress;
n.person = paramControls.personEditor.getText();
n.type = Notification::Type (paramControls.categoryComboBox .getSelectedItemIndex());
n.priority = Notification::Priority (paramControls.priorityComboBox .getSelectedItemIndex() - 2);
n.lockScreenAppearance = Notification::LockScreenAppearance (paramControls.lockScreenVisibilityComboBox.getSelectedItemIndex() - 1);
n.groupId = paramControls.groupIdEditor.getText();
n.groupSortKey = paramControls.sortKeyEditor.getText();
n.groupSummary = paramControls.groupSummaryButton.getToggleState();
n.groupAlertBehaviour = Notification::GroupAlertBehaviour (paramControls.groupAlertBehaviourComboBox.getSelectedItemIndex());
}
void fillOptionalParamsThree (PushNotifications::Notification& n)
{
n.accentColour = paramControls.accentColourButton.findColour (TextButton::buttonColourId, false);
n.ledColour = paramControls.ledColourButton .findColour (TextButton::buttonColourId, false);
using Notification = PushNotifications::Notification;
Notification::LedBlinkPattern ledBlinkPattern;
ledBlinkPattern.msToBeOn = paramControls.ledMsToBeOnComboBox .getSelectedItemIndex() * 200;
ledBlinkPattern.msToBeOff = paramControls.ledMsToBeOffComboBox.getSelectedItemIndex() * 200;
n.ledBlinkPattern = ledBlinkPattern;
Array<int> vibrationPattern;
if (paramControls.vibratorMsToBeOnComboBox .getSelectedItemIndex() > 0 &&
paramControls.vibratorMsToBeOffComboBox.getSelectedItemIndex() > 0)
{
vibrationPattern.add (paramControls.vibratorMsToBeOffComboBox.getSelectedItemIndex() * 500);
vibrationPattern.add (paramControls.vibratorMsToBeOnComboBox .getSelectedItemIndex() * 500);
vibrationPattern.add (2 * paramControls.vibratorMsToBeOffComboBox.getSelectedItemIndex() * 500);
vibrationPattern.add (2 * paramControls.vibratorMsToBeOnComboBox .getSelectedItemIndex() * 500);
}
n.vibrationPattern = vibrationPattern;
n.localOnly = paramControls.localOnlyButton.getToggleState();
n.ongoing = paramControls.ongoingButton.getToggleState();
n.timestampVisibility = Notification::TimestampVisibility (paramControls.timestampVisibilityComboBox.getSelectedItemIndex());
if (paramControls.timeoutAfterComboBox.getSelectedItemIndex() > 0)
{
auto index = paramControls.timeoutAfterComboBox.getSelectedItemIndex();
n.timeoutAfterMs = index * 1000 + 4000;
}
}
void setupAccentColour()
{
auto accentColourSelector = std::make_unique<ColourSelector>();
accentColourSelector->setName ("accent colour");
accentColourSelector->setCurrentColour (paramControls.accentColourButton.findColour (TextButton::buttonColourId));
accentColourSelector->setColour (ColourSelector::backgroundColourId, Colours::transparentBlack);
accentColourSelector->setSize (200, 200);
accentColourSelector->addComponentListener (this);
accentColourSelector->addChangeListener (this);
paramControls.accentColourSelector = accentColourSelector.get();
CallOutBox::launchAsynchronously (std::move (accentColourSelector), paramControls.accentColourButton.getScreenBounds(), nullptr);
}
void setupLedColour()
{
auto ledColourSelector = std::make_unique<ColourSelector>();
ledColourSelector->setName ("led colour");
ledColourSelector->setCurrentColour (paramControls.ledColourButton.findColour (TextButton::buttonColourId));
ledColourSelector->setColour (ColourSelector::backgroundColourId, Colours::transparentBlack);
ledColourSelector->setSize (200, 200);
ledColourSelector->addComponentListener (this);
ledColourSelector->addChangeListener (this);
paramControls.ledColourSelector = ledColourSelector.get();
CallOutBox::launchAsynchronously (std::move (ledColourSelector), paramControls.accentColourButton.getScreenBounds(), nullptr);
}
void changeListenerCallback (ChangeBroadcaster* source) override
{
if (source == paramControls.accentColourSelector)
{
auto c = paramControls.accentColourSelector->getCurrentColour();
paramControls.accentColourButton.setColour (TextButton::buttonColourId, c);
}
else if (source == paramControls.ledColourSelector)
{
auto c = paramControls.ledColourSelector->getCurrentColour();
paramControls.ledColourButton.setColour (TextButton::buttonColourId, c);
}
}
void componentBeingDeleted (Component& component) override
{
if (&component == paramControls.accentColourSelector)
paramControls.accentColourSelector = nullptr;
else if (&component == paramControls.ledColourSelector)
paramControls.ledColourSelector = nullptr;
}
void handleNotification (bool isLocalNotification, const PushNotifications::Notification& n) override
{
ignoreUnused (isLocalNotification);
NativeMessageBox::showAsync (MessageBoxOptions()
.withIconType (MessageBoxIconType::InfoIcon)
.withTitle ("Received notification")
.withMessage ("ID: " + n.identifier
+ ", title: " + n.title
+ ", body: " + n.body),
nullptr);
}
void handleNotificationAction (bool isLocalNotification,
const PushNotifications::Notification& n,
const juce::String& actionIdentifier,
const juce::String& optionalResponse) override
{
ignoreUnused (isLocalNotification);
NativeMessageBox::showAsync (MessageBoxOptions()
.withIconType (MessageBoxIconType::InfoIcon)
.withTitle ("Received notification action")
.withMessage ("ID: " + n.identifier
+ ", title: " + n.title
+ ", body: " + n.body
+ ", action: " + actionIdentifier
+ ", optionalResponse: " + optionalResponse),
nullptr);
PushNotifications::getInstance()->removeDeliveredNotification (n.identifier);
}
void localNotificationDismissedByUser (const PushNotifications::Notification& n) override
{
NativeMessageBox::showAsync (MessageBoxOptions()
.withIconType (MessageBoxIconType::InfoIcon)
.withTitle ("Notification dismissed by a user")
.withMessage ("ID: " + n.identifier
+ ", title: " + n.title
+ ", body: " + n.body),
nullptr);
}
void deliveredNotificationsListReceived (const Array<PushNotifications::Notification>& notifs) override
{
String text = "Received notifications: ";
for (auto& n : notifs)
text << "(" << n.identifier << ", " << n.title << ", " << n.body << "), ";
NativeMessageBox::showAsync (MessageBoxOptions()
.withIconType (MessageBoxIconType::InfoIcon)
.withTitle ("Received notification list")
.withMessage (text),
nullptr);
}
void pendingLocalNotificationsListReceived (const Array<PushNotifications::Notification>& notifs) override
{
String text = "Pending notifications: ";
for (auto& n : notifs)
text << "(" << n.identifier << ", " << n.title << ", " << n.body << "), ";
NativeMessageBox::showAsync (MessageBoxOptions()
.withIconType (MessageBoxIconType::InfoIcon)
.withTitle ("Pending notification list")
.withMessage (text),
nullptr);
}
void deviceTokenRefreshed (const String& token) override
{
NativeMessageBox::showAsync (MessageBoxOptions()
.withIconType (MessageBoxIconType::InfoIcon)
.withTitle ("Device token refreshed")
.withMessage (token),
nullptr);
}
#if JUCE_ANDROID
void remoteNotificationsDeleted() override
{
NativeMessageBox::showAsync (MessageBoxOptions()
.withIconType (MessageBoxIconType::InfoIcon)
.withTitle ("Remote notifications deleted")
.withMessage ("Some of the pending messages were removed!"),
nullptr);
}
void upstreamMessageSent (const String& messageId) override
{
NativeMessageBox::showAsync (MessageBoxOptions()
.withIconType (MessageBoxIconType::InfoIcon)
.withTitle ("Upstream message sent")
.withMessage ("Message id: " + messageId),
nullptr);
}
void upstreamMessageSendingError (const String& messageId, const String& error) override
{
NativeMessageBox::showAsync (MessageBoxOptions()
.withIconType (MessageBoxIconType::InfoIcon)
.withTitle ("Upstream message sending error")
.withMessage ("Message id: " + messageId
+ "\nerror: " + error),
nullptr);
}
static Array<PushNotifications::Channel> getAndroidChannels()
{
using Channel = PushNotifications::Channel;
Channel ch1, ch2, ch3;
ch1.identifier = "1";
ch1.name = "HighImportance";
ch1.importance = PushNotifications::Channel::max;
ch1.lockScreenAppearance = PushNotifications::Notification::showCompletely;
ch1.description = "High Priority Channel for important stuff";
ch1.groupId = "demoGroup";
ch1.ledColour = Colours::red;
ch1.bypassDoNotDisturb = true;
ch1.canShowBadge = true;
ch1.enableLights = true;
ch1.enableVibration = true;
ch1.soundToPlay = URL ("demonstrative");
ch1.vibrationPattern = { 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200 };
ch2.identifier = "2";
ch2.name = "MediumImportance";
ch2.importance = PushNotifications::Channel::normal;
ch2.lockScreenAppearance = PushNotifications::Notification::showPartially;
ch2.description = "Medium Priority Channel for standard stuff";
ch2.groupId = "demoGroup";
ch2.ledColour = Colours::yellow;
ch2.canShowBadge = true;
ch2.enableLights = true;
ch2.enableVibration = true;
ch2.soundToPlay = URL ("default_os_sound");
ch2.vibrationPattern = { 1000, 1000 };
ch3.identifier = "3";
ch3.name = "LowImportance";
ch3.importance = PushNotifications::Channel::min;
ch3.lockScreenAppearance = PushNotifications::Notification::dontShow;
ch3.description = "Low Priority Channel for silly stuff";
ch3.groupId = "demoGroup";
return { ch1, ch2, ch3 };
}
#elif JUCE_IOS || JUCE_MAC
static PushNotifications::Settings getNotificationSettings()
{
PushNotifications::Settings settings;
settings.allowAlert = true;
settings.allowBadge = true;
settings.allowSound = true;
#if JUCE_IOS
using Action = PushNotifications::Settings::Action;
using Category = PushNotifications::Settings::Category;
Action okAction;
okAction.identifier = "okAction";
okAction.title = "OK!";
okAction.style = Action::button;
okAction.triggerInBackground = true;
Action cancelAction;
cancelAction.identifier = "cancelAction";
cancelAction.title = "Cancel";
cancelAction.style = Action::button;
cancelAction.triggerInBackground = true;
cancelAction.destructive = true;
Action textAction;
textAction.identifier = "textAction";
textAction.title = "Enter text";
textAction.style = Action::text;
textAction.triggerInBackground = true;
textAction.destructive = false;
textAction.textInputButtonText = "Ok";
textAction.textInputPlaceholder = "Enter text...";
Category okCategory;
okCategory.identifier = "okCategory";
okCategory.actions = { okAction };
Category okCancelCategory;
okCancelCategory.identifier = "okCancelCategory";
okCancelCategory.actions = { okAction, cancelAction };
Category textCategory;
textCategory.identifier = "textCategory";
textCategory.actions = { textAction };
textCategory.sendDismissAction = true;
settings.categories = { okCategory, okCancelCategory, textCategory };
#endif
return settings;
}
#endif
struct RowComponent : public Component
{
RowComponent (Label& l, Component& c, int u = 1)
: label (l),
editor (c),
rowUnits (u)
{
addAndMakeVisible (label);
addAndMakeVisible (editor);
}
void resized() override
{
auto bounds = getLocalBounds();
label .setBounds (bounds.removeFromLeft (getWidth() / 3));
editor.setBounds (bounds);
}
Label& label;
Component& editor;
int rowUnits;
};
struct ParamControls
{
Label identifierLabel { "identifierLabel", "Identifier" };
TextEditor identifierEditor;
Label titleLabel { "titleLabel", "Title" };
TextEditor titleEditor;
Label bodyLabel { "bodyLabel", "Body" };
TextEditor bodyEditor;
Label categoryLabel { "categoryLabel", "Category" };
ComboBox categoryComboBox;
Label channelIdLabel { "channelIdLabel", "Channel ID" };
ComboBox channelIdComboBox;
Label iconLabel { "iconLabel", "Icon" };
ComboBox iconComboBox;
Label subtitleLabel { "subtitleLabel", "Subtitle" };
TextEditor subtitleEditor;
Label badgeNumberLabel { "badgeNumberLabel", "BadgeNumber" };
ComboBox badgeNumberComboBox;
Label soundToPlayLabel { "soundToPlayLabel", "SoundToPlay" };
ComboBox soundToPlayComboBox;
Label propertiesLabel { "propertiesLabel", "Properties" };
TextEditor propertiesEditor;
Label fireInLabel { "fireInLabel", "Fire in" };
ComboBox fireInComboBox;
Label repeatLabel { "repeatLabel", "Repeat" };
ToggleButton repeatButton;
Label largeIconLabel { "largeIconLabel", "Large Icon" };
ComboBox largeIconComboBox;
Label badgeIconLabel { "badgeIconLabel", "Badge Icon" };
ComboBox badgeIconComboBox;
Label tickerTextLabel { "tickerTextLabel", "Ticker Text" };
TextEditor tickerTextEditor;
Label autoCancelLabel { "autoCancelLabel", "AutoCancel" };
ToggleButton autoCancelButton;
Label alertOnlyOnceLabel { "alertOnlyOnceLabel", "AlertOnlyOnce" };
ToggleButton alertOnlyOnceButton;
Label actionsLabel { "actionsLabel", "Actions" };
ComboBox actionsComboBox;
Label progressMaxLabel { "progressMaxLabel", "ProgressMax" };
ComboBox progressMaxComboBox;
Label progressCurrentLabel { "progressCurrentLabel", "ProgressCurrent" };
ComboBox progressCurrentComboBox;
Label progressIndeterminateLabel { "progressIndeterminateLabel", "ProgressIndeterminate" };
ToggleButton progressIndeterminateButton;
Label notifCategoryLabel { "notifCategoryLabel", "Category" };
ComboBox notifCategoryComboBox;
Label priorityLabel { "priorityLabel", "Priority" };
ComboBox priorityComboBox;
Label personLabel { "personLabel", "Person" };
TextEditor personEditor;
Label lockScreenVisibilityLabel { "lockScreenVisibilityLabel", "LockScreenVisibility" };
ComboBox lockScreenVisibilityComboBox;
Label groupIdLabel { "groupIdLabel", "GroupID" };
TextEditor groupIdEditor;
Label sortKeyLabel { "sortKeyLabel", "SortKey" };
TextEditor sortKeyEditor;
Label groupSummaryLabel { "groupSummaryLabel", "GroupSummary" };
ToggleButton groupSummaryButton;
Label groupAlertBehaviourLabel { "groupAlertBehaviourLabel", "GroupAlertBehaviour" };
ComboBox groupAlertBehaviourComboBox;
Label accentColourLabel { "accentColourLabel", "AccentColour" };
TextButton accentColourButton;
Label ledColourLabel { "ledColourLabel", "LedColour" };
TextButton ledColourButton;
Label ledMsToBeOnLabel { "ledMsToBeOnLabel", "LedMsToBeOn" };
ComboBox ledMsToBeOnComboBox;
Label ledMsToBeOffLabel { "ledMsToBeOffLabel", "LedMsToBeOff" };
ComboBox ledMsToBeOffComboBox;
Label vibratorMsToBeOnLabel { "vibratorMsToBeOnLabel", "VibrationMsToBeOn" };
ComboBox vibratorMsToBeOnComboBox;
Label vibratorMsToBeOffLabel { "vibratorMsToBeOffLabel", "VibrationMsToBeOff" };
ComboBox vibratorMsToBeOffComboBox;
Label localOnlyLabel { "localOnlyLabel", "LocalOnly" };
ToggleButton localOnlyButton;
Label ongoingLabel { "ongoingLabel", "Ongoing" };
ToggleButton ongoingButton;
Label timestampVisibilityLabel { "timestampVisibilityLabel", "TimestampMode" };
ComboBox timestampVisibilityComboBox;
Label timeoutAfterLabel { "timeoutAfterLabel", "Timeout After Ms" };
ComboBox timeoutAfterComboBox;
ColourSelector* accentColourSelector = nullptr;
ColourSelector* ledColourSelector = nullptr;
};
void setupControls()
{
auto& pc = paramControls;
StringArray categories { "okCategory", "okCancelCategory", "textCategory" };
for (auto& c : categories)
pc.categoryComboBox.addItem (c, pc.categoryComboBox.getNumItems() + 1);
pc.categoryComboBox.setSelectedItemIndex (0);
for (auto i = 1; i <= 3; ++i)
pc.channelIdComboBox.addItem (String (i), i);
pc.channelIdComboBox.setSelectedItemIndex (0);
for (auto i = 0; i < 5; ++i)
pc.iconComboBox.addItem ("icon" + String (i + 1), i + 1);
pc.iconComboBox.setSelectedItemIndex (0);
#if JUCE_MAC
pc.iconComboBox.addItem ("none", 100);
#endif
pc.fireInComboBox.addItem ("Now", 1);
for (auto i = 1; i < 11; ++i)
pc.fireInComboBox.addItem (String (10 * i) + "seconds", i + 1);
pc.fireInComboBox.setSelectedItemIndex (0);
pc.largeIconComboBox.addItem ("none", 1);
for (auto i = 1; i < 5; ++i)
pc.largeIconComboBox.addItem ("icon" + String (i), i + 1);
pc.largeIconComboBox.setSelectedItemIndex (0);
pc.badgeIconComboBox.addItem ("none", 1);
pc.badgeIconComboBox.addItem ("small", 2);
pc.badgeIconComboBox.addItem ("large", 3);
pc.badgeIconComboBox.setSelectedItemIndex (2);
pc.actionsComboBox.addItem ("none", 1);
pc.actionsComboBox.addItem ("ok-cancel", 2);
pc.actionsComboBox.addItem ("text-input", 3);
#if JUCE_ANDROID
pc.actionsComboBox.addItem ("ok-cancel-icons", 4);
pc.actionsComboBox.addItem ("text-input-limited_responses", 5);
#endif
pc.actionsComboBox.setSelectedItemIndex (0);
for (auto i = 0; i < 7; ++i)
pc.badgeNumberComboBox.addItem (String (i), i + 1);
pc.badgeNumberComboBox.setSelectedItemIndex (0);
#if JUCE_IOS
String prefix = "Notifications/sounds/";
String extension = ".caf";
#else
String prefix;
String extension;
#endif
pc.soundToPlayComboBox.addItem ("none", 1);
pc.soundToPlayComboBox.addItem ("default_os_sound", 2);
pc.soundToPlayComboBox.addItem (prefix + "demonstrative" + extension, 3);
pc.soundToPlayComboBox.addItem (prefix + "isntit" + extension, 4);
pc.soundToPlayComboBox.addItem (prefix + "jinglebellssms" + extension, 5);
pc.soundToPlayComboBox.addItem (prefix + "served" + extension, 6);
pc.soundToPlayComboBox.addItem (prefix + "solemn" + extension, 7);
pc.soundToPlayComboBox.setSelectedItemIndex (1);
for (auto i = 0; i < 11; ++i)
{
pc.progressMaxComboBox .addItem (String (i * 10) + "%", i + 1);
pc.progressCurrentComboBox.addItem (String (i * 10) + "%", i + 1);
}
pc.progressMaxComboBox .setSelectedItemIndex (0);
pc.progressCurrentComboBox.setSelectedItemIndex (0);
pc.notifCategoryComboBox.addItem ("unspecified", 1);
pc.notifCategoryComboBox.addItem ("alarm", 2);
pc.notifCategoryComboBox.addItem ("call", 3);
pc.notifCategoryComboBox.addItem ("email", 4);
pc.notifCategoryComboBox.addItem ("error", 5);
pc.notifCategoryComboBox.addItem ("event", 6);
pc.notifCategoryComboBox.addItem ("message", 7);
pc.notifCategoryComboBox.addItem ("progress", 8);
pc.notifCategoryComboBox.addItem ("promo", 9);
pc.notifCategoryComboBox.addItem ("recommendation", 10);
pc.notifCategoryComboBox.addItem ("reminder", 11);
pc.notifCategoryComboBox.addItem ("service", 12);
pc.notifCategoryComboBox.addItem ("social", 13);
pc.notifCategoryComboBox.addItem ("status", 14);
pc.notifCategoryComboBox.addItem ("system", 15);
pc.notifCategoryComboBox.addItem ("transport", 16);
pc.notifCategoryComboBox.setSelectedItemIndex (0);
for (auto i = -2; i < 3; ++i)
pc.priorityComboBox.addItem (String (i), i + 3);
pc.priorityComboBox.setSelectedItemIndex (2);
pc.lockScreenVisibilityComboBox.addItem ("don't show", 1);
pc.lockScreenVisibilityComboBox.addItem ("show partially", 2);
pc.lockScreenVisibilityComboBox.addItem ("show completely", 3);
pc.lockScreenVisibilityComboBox.setSelectedItemIndex (1);
pc.groupAlertBehaviourComboBox.addItem ("alert all", 1);
pc.groupAlertBehaviourComboBox.addItem ("alert summary", 2);
pc.groupAlertBehaviourComboBox.addItem ("alert children", 3);
pc.groupAlertBehaviourComboBox.setSelectedItemIndex (0);
pc.timeoutAfterComboBox.addItem ("No timeout", 1);
for (auto i = 0; i < 10; ++i)
{
pc.ledMsToBeOnComboBox .addItem (String (i * 200) + "ms", i + 1);
pc.ledMsToBeOffComboBox .addItem (String (i * 200) + "ms", i + 1);
pc.vibratorMsToBeOnComboBox .addItem (String (i * 500) + "ms", i + 1);
pc.vibratorMsToBeOffComboBox.addItem (String (i * 500) + "ms", i + 1);
pc.timeoutAfterComboBox .addItem (String (5000 + 1000 * i) + "ms", i + 2);
}
pc.ledMsToBeOnComboBox .setSelectedItemIndex (5);
pc.ledMsToBeOffComboBox .setSelectedItemIndex (5);
pc.vibratorMsToBeOnComboBox .setSelectedItemIndex (0);
pc.vibratorMsToBeOffComboBox.setSelectedItemIndex (0);
pc.timeoutAfterComboBox .setSelectedItemIndex (0);
pc.timestampVisibilityComboBox.addItem ("off", 1);
pc.timestampVisibilityComboBox.addItem ("on", 2);
pc.timestampVisibilityComboBox.addItem ("chronometer", 3);
pc.timestampVisibilityComboBox.addItem ("count down", 4);
pc.timestampVisibilityComboBox.setSelectedItemIndex (1);
}
void distributeControls()
{
auto& pc = paramControls;
paramsOneView .addRowComponent (new RowComponent (pc.identifierLabel, pc.identifierEditor));
paramsOneView .addRowComponent (new RowComponent (pc.titleLabel, pc.titleEditor));
paramsOneView .addRowComponent (new RowComponent (pc.bodyLabel, pc.bodyEditor, 4));
#if JUCE_IOS
paramsOneView .addRowComponent (new RowComponent (pc.categoryLabel, pc.categoryComboBox));
#elif JUCE_ANDROID
paramsOneView .addRowComponent (new RowComponent (pc.channelIdLabel, pc.channelIdComboBox));
#endif
#if JUCE_ANDROID || JUCE_MAC
paramsOneView .addRowComponent (new RowComponent (pc.iconLabel, pc.iconComboBox));
#endif
paramsTwoView .addRowComponent (new RowComponent (pc.subtitleLabel, pc.subtitleEditor));
#if ! JUCE_MAC
paramsTwoView .addRowComponent (new RowComponent (pc.badgeNumberLabel, pc.badgeNumberComboBox));
#endif
paramsTwoView .addRowComponent (new RowComponent (pc.soundToPlayLabel, pc.soundToPlayComboBox));
paramsTwoView .addRowComponent (new RowComponent (pc.propertiesLabel, pc.propertiesEditor, 3));
#if JUCE_IOS || JUCE_MAC
paramsTwoView .addRowComponent (new RowComponent (pc.fireInLabel, pc.fireInComboBox));
paramsTwoView .addRowComponent (new RowComponent (pc.repeatLabel, pc.repeatButton));
#elif JUCE_ANDROID
paramsTwoView .addRowComponent (new RowComponent (pc.largeIconLabel, pc.largeIconComboBox));
paramsTwoView .addRowComponent (new RowComponent (pc.badgeIconLabel, pc.badgeIconComboBox));
paramsTwoView .addRowComponent (new RowComponent (pc.tickerTextLabel, pc.tickerTextEditor));
paramsTwoView .addRowComponent (new RowComponent (pc.autoCancelLabel, pc.autoCancelButton));
paramsTwoView .addRowComponent (new RowComponent (pc.alertOnlyOnceLabel, pc.alertOnlyOnceButton));
#endif
#if JUCE_ANDROID || JUCE_MAC
paramsTwoView .addRowComponent (new RowComponent (pc.actionsLabel, pc.actionsComboBox));
#endif
#if JUCE_ANDROID
paramsThreeView.addRowComponent (new RowComponent (pc.progressMaxLabel, pc.progressMaxComboBox));
paramsThreeView.addRowComponent (new RowComponent (pc.progressCurrentLabel, pc.progressCurrentComboBox));
paramsThreeView.addRowComponent (new RowComponent (pc.progressIndeterminateLabel, pc.progressIndeterminateButton));
paramsThreeView.addRowComponent (new RowComponent (pc.categoryLabel, pc.categoryComboBox));
paramsThreeView.addRowComponent (new RowComponent (pc.priorityLabel, pc.priorityComboBox));
paramsThreeView.addRowComponent (new RowComponent (pc.personLabel, pc.personEditor));
paramsThreeView.addRowComponent (new RowComponent (pc.lockScreenVisibilityLabel, pc.lockScreenVisibilityComboBox));
paramsThreeView.addRowComponent (new RowComponent (pc.groupIdLabel, pc.groupIdEditor));
paramsThreeView.addRowComponent (new RowComponent (pc.sortKeyLabel, pc.sortKeyEditor));
paramsThreeView.addRowComponent (new RowComponent (pc.groupSummaryLabel, pc.groupSummaryButton));
paramsThreeView.addRowComponent (new RowComponent (pc.groupAlertBehaviourLabel, pc.groupAlertBehaviourComboBox));
paramsFourView .addRowComponent (new RowComponent (pc.accentColourLabel, pc.accentColourButton));
paramsFourView .addRowComponent (new RowComponent (pc.ledColourLabel, pc.ledColourButton));
paramsFourView .addRowComponent (new RowComponent (pc.ledMsToBeOffLabel, pc.ledMsToBeOffComboBox));
paramsFourView .addRowComponent (new RowComponent (pc.ledMsToBeOnLabel, pc.ledMsToBeOnComboBox));
paramsFourView .addRowComponent (new RowComponent (pc.vibratorMsToBeOffLabel, pc.vibratorMsToBeOffComboBox));
paramsFourView .addRowComponent (new RowComponent (pc.vibratorMsToBeOnLabel, pc.vibratorMsToBeOnComboBox));
paramsFourView .addRowComponent (new RowComponent (pc.localOnlyLabel, pc.localOnlyButton));
paramsFourView .addRowComponent (new RowComponent (pc.ongoingLabel, pc.ongoingButton));
paramsFourView .addRowComponent (new RowComponent (pc.timestampVisibilityLabel, pc.timestampVisibilityComboBox));
paramsFourView .addRowComponent (new RowComponent (pc.timeoutAfterLabel, pc.timeoutAfterComboBox));
#endif
}
struct ParamsView : public Component
{
ParamsView()
{
// For now, to be able to dismiss mobile keyboard.
setWantsKeyboardFocus (true);
}
void addRowComponent (RowComponent* rc)
{
rowComponents.add (rc);
addAndMakeVisible (rc);
}
void resized() override
{
auto totalRowUnits = 0;
for (auto* rc : rowComponents)
totalRowUnits += rc->rowUnits;
auto rowHeight = getHeight() / totalRowUnits;
auto bounds = getLocalBounds();
for (auto* rc : rowComponents)
rc->setBounds (bounds.removeFromTop (rc->rowUnits * rowHeight));
auto* last = rowComponents[rowComponents.size() - 1];
last->setBounds (last->getBounds().withHeight (getHeight() - last->getY()));
}
private:
OwnedArray<RowComponent> rowComponents;
};
struct AuxActionsView : public Component
{
AuxActionsView()
{
addAndMakeVisible (getDeliveredNotificationsButton);
addAndMakeVisible (removeDeliveredNotifWithIdButton);
addAndMakeVisible (deliveredNotifIdentifier);
addAndMakeVisible (removeAllDeliveredNotifsButton);
#if JUCE_IOS || JUCE_MAC
addAndMakeVisible (getPendingNotificationsButton);
addAndMakeVisible (removePendingNotifWithIdButton);
addAndMakeVisible (pendingNotifIdentifier);
addAndMakeVisible (removeAllPendingNotifsButton);
#endif
// For now, to be able to dismiss mobile keyboard.
setWantsKeyboardFocus (true);
}
void resized() override
{
auto columnWidth = getWidth();
auto rowHeight = getHeight() / 6;
auto bounds = getLocalBounds();
getDeliveredNotificationsButton .setBounds (bounds.removeFromTop (rowHeight));
auto rowBounds = bounds.removeFromTop (rowHeight);
removeDeliveredNotifWithIdButton.setBounds (rowBounds.removeFromLeft (columnWidth / 2));
deliveredNotifIdentifier .setBounds (rowBounds);
removeAllDeliveredNotifsButton .setBounds (bounds.removeFromTop (rowHeight));
#if JUCE_IOS || JUCE_MAC
getPendingNotificationsButton .setBounds (bounds.removeFromTop (rowHeight));
rowBounds = bounds.removeFromTop (rowHeight);
removePendingNotifWithIdButton.setBounds (rowBounds.removeFromLeft (columnWidth / 2));
pendingNotifIdentifier .setBounds (rowBounds);
removeAllPendingNotifsButton .setBounds (bounds.removeFromTop (rowHeight));
#endif
}
TextButton getDeliveredNotificationsButton { "Get Delivered Notifications" };
TextButton removeDeliveredNotifWithIdButton { "Remove Delivered Notif With ID:" };
TextEditor deliveredNotifIdentifier;
TextButton removeAllDeliveredNotifsButton { "Remove All Delivered Notifs" };
TextButton getPendingNotificationsButton { "Get Pending Notifications" };
TextButton removePendingNotifWithIdButton { "Remove Pending Notif With ID:" };
TextEditor pendingNotifIdentifier;
TextButton removeAllPendingNotifsButton { "Remove All Pending Notifs" };
};
struct RemoteView : public Component
{
RemoteView()
{
addAndMakeVisible (getDeviceTokenButton);
#if JUCE_ANDROID
addAndMakeVisible (sendRemoteMessageButton);
addAndMakeVisible (subscribeToSportsButton);
addAndMakeVisible (unsubscribeFromSportsButton);
#endif
}
void resized()
{
auto rowSize = getHeight () / 10;
auto bounds = getLocalBounds().reduced (getWidth() / 10, getHeight() / 10);
bounds.removeFromTop (2 * rowSize);
getDeviceTokenButton .setBounds (bounds.removeFromTop (rowSize));
sendRemoteMessageButton .setBounds (bounds.removeFromTop (rowSize));
subscribeToSportsButton .setBounds (bounds.removeFromTop (rowSize));
unsubscribeFromSportsButton.setBounds (bounds.removeFromTop (rowSize));
}
TextButton getDeviceTokenButton { "GetDeviceToken" };
TextButton sendRemoteMessageButton { "SendRemoteMessage" };
TextButton subscribeToSportsButton { "SubscribeToSports" };
TextButton unsubscribeFromSportsButton { "UnsubscribeFromSports" };
};
struct DemoTabbedComponent : public TabbedComponent
{
explicit DemoTabbedComponent (TabbedButtonBar::Orientation orientation)
: TabbedComponent (orientation)
{
}
void currentTabChanged (int, const String& newCurrentTabName) override
{
if (! showedRemoteInstructions && newCurrentTabName == "Remote")
{
PushNotificationsDemo::showRemoteInstructions();
showedRemoteInstructions = true;
}
}
private:
bool showedRemoteInstructions = false;
};
static void showRemoteInstructions()
{
#if JUCE_IOS || JUCE_MAC
NativeMessageBox::showAsync (MessageBoxOptions()
.withIconType (MessageBoxIconType::InfoIcon)
.withTitle ("Remote Notifications instructions")
.withMessage ("In order to be able to test remote notifications "
"ensure that the app is signed and that you register "
"the bundle ID for remote notifications in "
"Apple Developer Center."),
nullptr);
#endif
}
Label headerLabel { "headerLabel", "Push Notifications Demo" };
ParamControls paramControls;
ParamsView paramsOneView, paramsTwoView, paramsThreeView, paramsFourView;
AuxActionsView auxActionsView;
TabbedComponent localNotificationsTabs { TabbedButtonBar::TabsAtTop };
RemoteView remoteView;
DemoTabbedComponent mainTabs { TabbedButtonBar::TabsAtTop };
TextButton sendButton { "Send!" };
Label notAvailableYetLabel { "notAvailableYetLabel",
"Push Notifications feature is not available on this platform yet!" };
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PushNotificationsDemo)
};