diff --git a/examples/PushNotificationsDemo/PushNotificationsDemo.jucer b/examples/PushNotificationsDemo/PushNotificationsDemo.jucer
index cc81f30f11..5fcfeffdb1 100644
--- a/examples/PushNotificationsDemo/PushNotificationsDemo.jucer
+++ b/examples/PushNotificationsDemo/PushNotificationsDemo.jucer
@@ -31,19 +31,25 @@
+ file="BinaryResources/sounds/demonstrative.caf" xcodeResource="0"/>
-
-
+ file="BinaryResources/sounds/demonstrative.mp3" xcodeResource="1"/>
+
+
+ file="BinaryResources/sounds/jinglebellssms.caf" xcodeResource="0"/>
-
-
-
-
+ file="BinaryResources/sounds/jinglebellssms.mp3" xcodeResource="1"/>
+
+
+
+
@@ -104,7 +110,7 @@
-
+
diff --git a/examples/PushNotificationsDemo/Source/MainComponent.cpp b/examples/PushNotificationsDemo/Source/MainComponent.cpp
index 3712f03e10..b183fd74a2 100644
--- a/examples/PushNotificationsDemo/Source/MainComponent.cpp
+++ b/examples/PushNotificationsDemo/Source/MainComponent.cpp
@@ -29,7 +29,10 @@
//==============================================================================
MainContentComponent::MainContentComponent()
{
- #if JUCE_ANDROID || JUCE_IOS
+ setupControls();
+ distributeControls();
+
+ #if JUCE_PUSH_NOTIFICATIONS
addAndMakeVisible (headerLabel);
addAndMakeVisible (mainTabs);
addAndMakeVisible (sendButton);
@@ -40,12 +43,18 @@ MainContentComponent::MainContentComponent()
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
+
const auto colour = getLookAndFeel().findColour (ResizableWindow::backgroundColourId);
- localNotificationsTabs.addTab ("Req. params", colour, &requiredParamsView, false);
- localNotificationsTabs.addTab ("Opt. params1", colour, &optionalParamsOneView, false);
+ localNotificationsTabs.addTab (tabNames[0], colour, ¶msOneView, false);
+ localNotificationsTabs.addTab (tabNames[1], colour, ¶msTwoView, false);
#if JUCE_ANDROID
- localNotificationsTabs.addTab ("Opt. params2", colour, &optionalParamsTwoView, false);
- localNotificationsTabs.addTab ("Opt. params3", colour, &optionalParamsThreeView, false);
+ localNotificationsTabs.addTab (tabNames[2], colour, ¶msThreeView, false);
+ localNotificationsTabs.addTab (tabNames[3], colour, ¶msFourView, false);
#endif
localNotificationsTabs.addTab ("Aux. actions", colour, &auxActionsView, false);
@@ -63,7 +72,7 @@ MainContentComponent::MainContentComponent()
auxActionsView.getDeliveredNotificationsButton .addListener (this);
auxActionsView.removeDeliveredNotifWithIdButton.addListener (this);
auxActionsView.removeAllDeliveredNotifsButton .addListener (this);
- #if JUCE_IOS
+ #if JUCE_IOS || JUCE_MAC
auxActionsView.getPendingNotificationsButton .addListener (this);
auxActionsView.removePendingNotifWithIdButton.addListener (this);
auxActionsView.removeAllPendingNotifsButton .addListener (this);
@@ -74,16 +83,16 @@ MainContentComponent::MainContentComponent()
remoteView.subscribeToSportsButton .addListener (this);
remoteView.unsubscribeFromSportsButton.addListener (this);
- optionalParamsThreeView.accentColourButton.addListener (this);
- optionalParamsThreeView.ledColourButton .addListener (this);
+ paramControls.accentColourButton.addListener (this);
+ paramControls.ledColourButton .addListener (this);
jassert (PushNotifications::getInstance()->areNotificationsEnabled());
PushNotifications::getInstance()->addListener (this);
- #if JUCE_IOS
- optionalParamsOneView.fireInComboBox.addListener (this);
- PushNotifications::getInstance()->requestPermissionsWithSettings (getIosSettings());
+ #if JUCE_IOS || JUCE_MAC
+ paramControls.fireInComboBox.addListener (this);
+ PushNotifications::getInstance()->requestPermissionsWithSettings (getNotificationSettings());
#elif JUCE_ANDROID
PushNotifications::ChannelGroup cg { "demoGroup", "demo group" };
PushNotifications::getInstance()->setupChannels ({{ cg }}, getAndroidChannels());
@@ -95,6 +104,200 @@ MainContentComponent::~MainContentComponent()
PushNotifications::getInstance()->removeListener (this);
}
+void MainContentComponent::setupControls()
+{
+ auto& pc = paramControls;
+
+ StringArray categories { "okCategory", "okCancelCategory", "textCategory" };
+
+ for (const auto& c : categories)
+ pc.categoryComboBox.addItem (c, pc.categoryComboBox.getNumItems() + 1);
+ pc.categoryComboBox.setSelectedItemIndex (0);
+
+ for (int i = 1; i <= 3; ++i)
+ pc.channelIdComboBox.addItem (String (i), i);
+ pc.channelIdComboBox.setSelectedItemIndex (0);
+
+ for (int 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 (int i = 1; i < 11; ++i)
+ pc.fireInComboBox.addItem (String (10 * i) + "seconds", i + 1);
+ pc.fireInComboBox.setSelectedItemIndex (0);
+
+ pc.largeIconComboBox.addItem ("none", 1);
+
+ for (int 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 (int i = 0; i < 7; ++i)
+ pc.badgeNumberComboBox.addItem (String (i), i + 1);
+ pc.badgeNumberComboBox.setSelectedItemIndex (0);
+
+ #if JUCE_IOS
+ String prefix = "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 (int 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 (int 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 (int 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 MainContentComponent::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
+}
+
void MainContentComponent::paint (Graphics& g)
{
g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
@@ -117,9 +320,9 @@ void MainContentComponent::buttonClicked (Button* b)
{
if (b == &sendButton)
sendLocalNotification();
- else if (b == &optionalParamsThreeView.accentColourButton)
+ else if (b == ¶mControls.accentColourButton)
setupAccentColour();
- else if (b == &optionalParamsThreeView.ledColourButton)
+ else if (b == ¶mControls.ledColourButton)
setupLedColour();
else if (b == &auxActionsView.getDeliveredNotificationsButton)
getDeliveredNotifications();
@@ -127,7 +330,7 @@ void MainContentComponent::buttonClicked (Button* b)
PushNotifications::getInstance()->removeDeliveredNotification (auxActionsView.deliveredNotifIdentifier.getText());
else if (b == &auxActionsView.removeAllDeliveredNotifsButton)
PushNotifications::getInstance()->removeAllDeliveredNotifications();
- #if JUCE_IOS
+ #if JUCE_IOS || JUCE_MAC
else if (b == &auxActionsView.getPendingNotificationsButton)
PushNotifications::getInstance()->getPendingLocalNotifications();
else if (b == &auxActionsView.removePendingNotifWithIdButton)
@@ -141,7 +344,10 @@ void MainContentComponent::buttonClicked (Button* b)
DBG ("token = " + token);
- NativeMessageBox::showMessageBoxAsync (AlertWindow::InfoIcon, "Device token", token);
+ if (token.isEmpty())
+ showRemoteInstructions();
+ else
+ NativeMessageBox::showMessageBoxAsync (AlertWindow::InfoIcon, "Device token", token);
}
#if JUCE_ANDROID
else if (b == &remoteView.sendRemoteMessageButton)
@@ -172,19 +378,15 @@ void MainContentComponent::buttonClicked (Button* b)
void MainContentComponent::comboBoxChanged (ComboBox* comboBoxThatHasChanged)
{
- #if JUCE_IOS
- if (comboBoxThatHasChanged == &optionalParamsOneView.fireInComboBox)
+ if (comboBoxThatHasChanged == ¶mControls.fireInComboBox)
{
- const bool repeatsAllowed = optionalParamsOneView.fireInComboBox.getSelectedItemIndex() >= 6;
+ const bool repeatsAllowed = paramControls.fireInComboBox.getSelectedItemIndex() >= 6;
- optionalParamsOneView.repeatButton.setEnabled (repeatsAllowed);
+ paramControls.repeatButton.setEnabled (repeatsAllowed);
if (! repeatsAllowed)
- optionalParamsOneView.repeatButton.setToggleState (false, NotificationType::sendNotification);
+ paramControls.repeatButton.setToggleState (false, NotificationType::sendNotification);
}
- #else
- ignoreUnused (comboBoxThatHasChanged);
- #endif
}
void MainContentComponent::sendLocalNotification()
@@ -222,23 +424,32 @@ void MainContentComponent::sendLocalNotification()
void MainContentComponent::fillRequiredParams (PushNotifications::Notification& n)
{
- n.identifier = requiredParamsView.identifierEditor.getText();
- n.title = requiredParamsView.titleEditor.getText();
- n.body = requiredParamsView.bodyEditor.getText();
+ n.identifier = paramControls.identifierEditor.getText();
+ n.title = paramControls.titleEditor.getText();
+ n.body = paramControls.bodyEditor.getText();
#if JUCE_IOS
- n.category = requiredParamsView.categories[requiredParamsView.categoryComboBox.getSelectedItemIndex()];
- #elif JUCE_ANDROID
- if (requiredParamsView.iconComboBox.getSelectedItemIndex() == 0)
- n.icon = "ic_stat_name";
- else if (requiredParamsView.iconComboBox.getSelectedItemIndex() == 1)
- n.icon = "ic_stat_name2";
- else if (requiredParamsView.iconComboBox.getSelectedItemIndex() == 2)
- n.icon = "ic_stat_name3";
- else if (requiredParamsView.iconComboBox.getSelectedItemIndex() == 3)
- n.icon = "ic_stat_name4";
- else
- n.icon = "ic_stat_name5";
+ n.category = paramControls.categoryComboBox.getText();
+ #elif JUCE_ANDROID || JUCE_MAC
+ #if JUCE_MAC
+ String prefix = "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 = new PushNotifications::Notification();
n.publicVersion->identifier = "blahblahblah";
@@ -247,69 +458,59 @@ void MainContentComponent::fillRequiredParams (PushNotifications::Notification&
n.publicVersion->icon = n.icon;
#if __ANDROID_API__ >= 26
- n.channelId = String (requiredParamsView.channelIdComboBox.getSelectedItemIndex() + 1);
+ n.channelId = String (paramControls.channelIdComboBox.getSelectedItemIndex() + 1);
#endif
#endif
}
void MainContentComponent::fillOptionalParamsOne (PushNotifications::Notification& n)
{
- n.subtitle = optionalParamsOneView.subtitleEditor.getText();
- n.badgeNumber = optionalParamsOneView.badgeNumberComboBox.getSelectedItemIndex();
+ n.subtitle = paramControls.subtitleEditor.getText();
+ n.badgeNumber = paramControls.badgeNumberComboBox.getSelectedItemIndex();
- if (optionalParamsOneView.soundToPlayComboBox.getSelectedItemIndex() > 0)
- n.soundToPlay = URL (optionalParamsOneView.soundToPlayComboBox.getItemText (optionalParamsOneView.soundToPlayComboBox.getSelectedItemIndex()));
+ if (paramControls.soundToPlayComboBox.getSelectedItemIndex() > 0)
+ n.soundToPlay = URL (paramControls.soundToPlayComboBox.getItemText (paramControls.soundToPlayComboBox.getSelectedItemIndex()));
- n.properties = JSON::parse (optionalParamsOneView.propertiesEditor.getText());
+ n.properties = JSON::parse (paramControls.propertiesEditor.getText());
- #if JUCE_IOS
- n.triggerIntervalSec = double (optionalParamsOneView.fireInComboBox.getSelectedItemIndex() * 10);
- n.repeat = optionalParamsOneView.repeatButton.getToggleState();
+ #if JUCE_IOS || JUCE_MAC
+ n.triggerIntervalSec = double (paramControls.fireInComboBox.getSelectedItemIndex() * 10);
+ n.repeat = paramControls.repeatButton.getToggleState();
#elif JUCE_ANDROID
- if (optionalParamsOneView.largeIconComboBox.getSelectedItemIndex() == 1)
+ if (paramControls.largeIconComboBox.getSelectedItemIndex() == 1)
n.largeIcon = ImageFileFormat::loadFrom (BinaryData::ic_stat_name6_png, BinaryData::ic_stat_name6_pngSize);
- else if (optionalParamsOneView.largeIconComboBox.getSelectedItemIndex() == 2)
+ else if (paramControls.largeIconComboBox.getSelectedItemIndex() == 2)
n.largeIcon = ImageFileFormat::loadFrom (BinaryData::ic_stat_name7_png, BinaryData::ic_stat_name7_pngSize);
- else if (optionalParamsOneView.largeIconComboBox.getSelectedItemIndex() == 3)
+ else if (paramControls.largeIconComboBox.getSelectedItemIndex() == 3)
n.largeIcon = ImageFileFormat::loadFrom (BinaryData::ic_stat_name8_png, BinaryData::ic_stat_name8_pngSize);
- else if (optionalParamsOneView.largeIconComboBox.getSelectedItemIndex() == 4)
+ else if (paramControls.largeIconComboBox.getSelectedItemIndex() == 4)
n.largeIcon = ImageFileFormat::loadFrom (BinaryData::ic_stat_name9_png, BinaryData::ic_stat_name9_pngSize);
- else if (optionalParamsOneView.largeIconComboBox.getSelectedItemIndex() == 5)
+ else if (paramControls.largeIconComboBox.getSelectedItemIndex() == 5)
n.largeIcon = ImageFileFormat::loadFrom (BinaryData::ic_stat_name10_png, BinaryData::ic_stat_name10_pngSize);
- n.badgeIconType = (PushNotifications::Notification::BadgeIconType) optionalParamsOneView.badgeIconComboBox.getSelectedItemIndex();
- n.tickerText = optionalParamsOneView.tickerTextEditor.getText();
+ n.badgeIconType = (PushNotifications::Notification::BadgeIconType) paramControls.badgeIconComboBox.getSelectedItemIndex();
+ n.tickerText = paramControls.tickerTextEditor.getText();
- n.shouldAutoCancel = optionalParamsOneView.autoCancelButton.getToggleState();
- n.alertOnlyOnce = optionalParamsOneView.alertOnlyOnceButton.getToggleState();
+ n.shouldAutoCancel = paramControls.autoCancelButton.getToggleState();
+ n.alertOnlyOnce = paramControls.alertOnlyOnceButton.getToggleState();
+ #endif
- if (optionalParamsOneView.actionsComboBox.getSelectedItemIndex() == 1)
+ #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 = "Ok";
- a2.title = "Cancel";
+ a .title = a .identifier = "Ok";
+ a2.title = a2.identifier = "Cancel";
n.actions.add (a);
n.actions.add (a2);
}
- else if (optionalParamsOneView.actionsComboBox.getSelectedItemIndex() == 2)
+ else if (paramControls.actionsComboBox.getSelectedItemIndex() == 2)
{
PushNotifications::Notification::Action a, a2;
- a .title = "Ok";
- a2.title = "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 (optionalParamsOneView.actionsComboBox.getSelectedItemIndex() == 3)
- {
- PushNotifications::Notification::Action a, a2;
- a .title = "Input Text Here";
- a2.title = "No";
+ 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";
@@ -318,11 +519,23 @@ void MainContentComponent::fillOptionalParamsOne (PushNotifications::Notificatio
n.actions.add (a);
n.actions.add (a2);
}
- else if (optionalParamsOneView.actionsComboBox.getSelectedItemIndex() == 4)
+ else if (paramControls.actionsComboBox.getSelectedItemIndex() == 3)
{
PushNotifications::Notification::Action a, a2;
- a .title = "Input Text Here";
- a2.title = "No";
+ 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";
@@ -342,102 +555,102 @@ void MainContentComponent::fillOptionalParamsTwo (PushNotifications::Notificatio
using Notification = PushNotifications::Notification;
Notification::Progress progress;
- progress.max = optionalParamsTwoView.progressMaxComboBox.getSelectedItemIndex() * 10;
- progress.current = optionalParamsTwoView.progressCurrentComboBox.getSelectedItemIndex() * 10;
- progress.indeterminate = optionalParamsTwoView.progressIndeterminateButton.getToggleState();
+ progress.max = paramControls.progressMaxComboBox.getSelectedItemIndex() * 10;
+ progress.current = paramControls.progressCurrentComboBox.getSelectedItemIndex() * 10;
+ progress.indeterminate = paramControls.progressIndeterminateButton.getToggleState();
n.progress = progress;
- n.person = optionalParamsTwoView.personEditor.getText();
- n.type = Notification::Type (optionalParamsTwoView.categoryComboBox.getSelectedItemIndex());
- n.priority = Notification::Priority (optionalParamsTwoView.priorityComboBox.getSelectedItemIndex() - 2);
- n.lockScreenAppearance = Notification::LockScreenAppearance (optionalParamsTwoView.lockScreenVisibilityComboBox.getSelectedItemIndex() - 1);
- n.groupId = optionalParamsTwoView.groupIdEditor.getText();
- n.groupSortKey = optionalParamsTwoView.sortKeyEditor.getText();
- n.groupSummary = optionalParamsTwoView.groupSummaryButton.getToggleState();
- n.groupAlertBehaviour = Notification::GroupAlertBehaviour (optionalParamsTwoView.groupAlertBehaviourComboBox.getSelectedItemIndex());
+ 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 MainContentComponent::fillOptionalParamsThree (PushNotifications::Notification& n)
{
- n.accentColour = optionalParamsThreeView.accentColourButton.findColour (TextButton::buttonColourId, false);
- n.ledColour = optionalParamsThreeView.ledColourButton .findColour (TextButton::buttonColourId, false);
+ 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 = optionalParamsThreeView.ledMsToBeOnComboBox .getSelectedItemIndex() * 200;
- ledBlinkPattern.msToBeOff = optionalParamsThreeView.ledMsToBeOffComboBox.getSelectedItemIndex() * 200;
+ ledBlinkPattern.msToBeOn = paramControls.ledMsToBeOnComboBox .getSelectedItemIndex() * 200;
+ ledBlinkPattern.msToBeOff = paramControls.ledMsToBeOffComboBox.getSelectedItemIndex() * 200;
n.ledBlinkPattern = ledBlinkPattern;
Array vibrationPattern;
- if (optionalParamsThreeView.vibratorMsToBeOnComboBox .getSelectedItemIndex() > 0 &&
- optionalParamsThreeView.vibratorMsToBeOffComboBox.getSelectedItemIndex() > 0)
+ if (paramControls.vibratorMsToBeOnComboBox .getSelectedItemIndex() > 0 &&
+ paramControls.vibratorMsToBeOffComboBox.getSelectedItemIndex() > 0)
{
- vibrationPattern.add (optionalParamsThreeView.vibratorMsToBeOffComboBox.getSelectedItemIndex() * 500);
- vibrationPattern.add (optionalParamsThreeView.vibratorMsToBeOnComboBox .getSelectedItemIndex() * 500);
- vibrationPattern.add (2 * optionalParamsThreeView.vibratorMsToBeOffComboBox.getSelectedItemIndex() * 500);
- vibrationPattern.add (2 * optionalParamsThreeView.vibratorMsToBeOnComboBox .getSelectedItemIndex() * 500);
+ 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 = optionalParamsThreeView.localOnlyButton.getToggleState();
- n.ongoing = optionalParamsThreeView.ongoingButton.getToggleState();
- n.timestampVisibility = Notification::TimestampVisibility (optionalParamsThreeView.timestampVisibilityComboBox.getSelectedItemIndex());
+ n.localOnly = paramControls.localOnlyButton.getToggleState();
+ n.ongoing = paramControls.ongoingButton.getToggleState();
+ n.timestampVisibility = Notification::TimestampVisibility (paramControls.timestampVisibilityComboBox.getSelectedItemIndex());
- if (optionalParamsThreeView.timeoutAfterComboBox.getSelectedItemIndex() > 0)
+ if (paramControls.timeoutAfterComboBox.getSelectedItemIndex() > 0)
{
- auto index = optionalParamsThreeView.timeoutAfterComboBox.getSelectedItemIndex();
+ auto index = paramControls.timeoutAfterComboBox.getSelectedItemIndex();
n.timeoutAfterMs = index * 1000 + 4000;
}
}
void MainContentComponent::setupAccentColour()
{
- optionalParamsThreeView.accentColourSelector = new ColourSelector();
- optionalParamsThreeView.accentColourSelector->setName ("accent colour");
- optionalParamsThreeView.accentColourSelector->setCurrentColour (optionalParamsThreeView.accentColourButton.findColour (TextButton::buttonColourId));
- optionalParamsThreeView.accentColourSelector->setColour (ColourSelector::backgroundColourId, Colours::transparentBlack);
- optionalParamsThreeView.accentColourSelector->setSize (200, 200);
- optionalParamsThreeView.accentColourSelector->addComponentListener (this);
- optionalParamsThreeView.accentColourSelector->addChangeListener (this);
+ paramControls.accentColourSelector = new ColourSelector();
+ paramControls.accentColourSelector->setName ("accent colour");
+ paramControls.accentColourSelector->setCurrentColour (paramControls.accentColourButton.findColour (TextButton::buttonColourId));
+ paramControls.accentColourSelector->setColour (ColourSelector::backgroundColourId, Colours::transparentBlack);
+ paramControls.accentColourSelector->setSize (200, 200);
+ paramControls.accentColourSelector->addComponentListener (this);
+ paramControls.accentColourSelector->addChangeListener (this);
- CallOutBox::launchAsynchronously (optionalParamsThreeView.accentColourSelector, optionalParamsThreeView.accentColourButton.getScreenBounds(), nullptr);
+ CallOutBox::launchAsynchronously (paramControls.accentColourSelector, paramControls.accentColourButton.getScreenBounds(), nullptr);
}
void MainContentComponent::setupLedColour()
{
- optionalParamsThreeView.ledColourSelector = new ColourSelector();
- optionalParamsThreeView.ledColourSelector->setName ("led colour");
- optionalParamsThreeView.ledColourSelector->setCurrentColour (optionalParamsThreeView.ledColourButton.findColour (TextButton::buttonColourId));
- optionalParamsThreeView.ledColourSelector->setColour (ColourSelector::backgroundColourId, Colours::transparentBlack);
- optionalParamsThreeView.ledColourSelector->setSize (200, 200);
- optionalParamsThreeView.ledColourSelector->addComponentListener (this);
- optionalParamsThreeView.ledColourSelector->addChangeListener (this);
+ paramControls.ledColourSelector = new ColourSelector();
+ paramControls.ledColourSelector->setName ("led colour");
+ paramControls.ledColourSelector->setCurrentColour (paramControls.ledColourButton.findColour (TextButton::buttonColourId));
+ paramControls.ledColourSelector->setColour (ColourSelector::backgroundColourId, Colours::transparentBlack);
+ paramControls.ledColourSelector->setSize (200, 200);
+ paramControls.ledColourSelector->addComponentListener (this);
+ paramControls.ledColourSelector->addChangeListener (this);
- CallOutBox::launchAsynchronously (optionalParamsThreeView.ledColourSelector, optionalParamsThreeView.accentColourButton.getScreenBounds(), nullptr);
+ CallOutBox::launchAsynchronously (paramControls.ledColourSelector, paramControls.accentColourButton.getScreenBounds(), nullptr);
}
void MainContentComponent::changeListenerCallback (ChangeBroadcaster* source)
{
- if (source == optionalParamsThreeView.accentColourSelector)
+ if (source == paramControls.accentColourSelector)
{
- Colour c = optionalParamsThreeView.accentColourSelector->getCurrentColour();
- optionalParamsThreeView.accentColourButton.setColour (TextButton::buttonColourId, c);
+ Colour c = paramControls.accentColourSelector->getCurrentColour();
+ paramControls.accentColourButton.setColour (TextButton::buttonColourId, c);
}
- else if (source == optionalParamsThreeView.ledColourSelector)
+ else if (source == paramControls.ledColourSelector)
{
- Colour c = optionalParamsThreeView.ledColourSelector->getCurrentColour();
- optionalParamsThreeView.ledColourButton.setColour (TextButton::buttonColourId, c);
+ Colour c = paramControls.ledColourSelector->getCurrentColour();
+ paramControls.ledColourButton.setColour (TextButton::buttonColourId, c);
}
}
void MainContentComponent::componentBeingDeleted (Component& component)
{
- if (&component == optionalParamsThreeView.accentColourSelector)
- optionalParamsThreeView.accentColourSelector = nullptr;
- else if (&component == optionalParamsThreeView.ledColourSelector)
- optionalParamsThreeView.ledColourSelector = nullptr;
+ if (&component == paramControls.accentColourSelector)
+ paramControls.accentColourSelector = nullptr;
+ else if (&component == paramControls.ledColourSelector)
+ paramControls.ledColourSelector = nullptr;
}
void MainContentComponent::handleNotification (bool isLocalNotification, const PushNotifications::Notification& n)
@@ -576,9 +789,15 @@ Array MainContentComponent::getAndroidChannels()
return { ch1, ch2, ch3 };
}
-#elif JUCE_IOS
-PushNotifications::Settings MainContentComponent::getIosSettings()
+#elif JUCE_IOS || JUCE_MAC
+PushNotifications::Settings MainContentComponent::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;
@@ -617,11 +836,8 @@ PushNotifications::Settings MainContentComponent::getIosSettings()
textCategory.actions = { textAction };
textCategory.sendDismissAction = true;
- PushNotifications::Settings settings;
- settings.allowAlert = true;
- settings.allowBadge = true;
- settings.allowSound = true;
settings.categories = { okCategory, okCancelCategory, textCategory };
+ #endif
return settings;
}
diff --git a/examples/PushNotificationsDemo/Source/MainComponent.h b/examples/PushNotificationsDemo/Source/MainComponent.h
index 5e819de386..2212a86e40 100644
--- a/examples/PushNotificationsDemo/Source/MainComponent.h
+++ b/examples/PushNotificationsDemo/Source/MainComponent.h
@@ -88,230 +88,61 @@ private:
void upstreamMessageSendingError (const String& messageId, const String& error) override;
static Array getAndroidChannels();
- #elif JUCE_IOS
- static PushNotifications::Settings getIosSettings();
+ #elif JUCE_IOS || JUCE_MAC
+ static PushNotifications::Settings getNotificationSettings();
#endif
- struct RequiredParamsView : public Component
+ struct RowComponent : public Component
{
- RequiredParamsView()
+ RowComponent (Label& l, Component& c, int u = 1)
+ : label (l),
+ editor (c),
+ rowUnits (u)
{
- addAndMakeVisible (identifierLabel);
- addAndMakeVisible (identifierEditor);
- addAndMakeVisible (titleLabel);
- addAndMakeVisible (titleEditor);
- addAndMakeVisible (bodyLabel);
- addAndMakeVisible (bodyEditor);
- #if JUCE_IOS
- addAndMakeVisible (categoryLabel);
- addAndMakeVisible (categoryComboBox);
-
- categories.add ("okCategory");
- categories.add ("okCancelCategory");
- categories.add ("textCategory");
-
- for (const auto& c : categories)
- categoryComboBox.addItem (c, categoryComboBox.getNumItems() + 1);
- categoryComboBox.setSelectedItemIndex (0);
-
- #elif JUCE_ANDROID
- #if __ANDROID_API__ >= 26
- addAndMakeVisible (channelIdLabel);
- addAndMakeVisible (channelIdComboBox);
-
- for (int i = 1; i <= 3; ++i)
- channelIdComboBox.addItem (String (i), i);
- channelIdComboBox.setSelectedItemIndex (0);
-
- #endif
- addAndMakeVisible (iconLabel);
- addAndMakeVisible (iconComboBox);
-
- for (int i = 0; i < 5; ++i)
- iconComboBox.addItem ("icon" + String (i + 1), i + 1);
- iconComboBox.setSelectedItemIndex (0);
- #endif
-
- // For now, to be able to dismiss mobile keyboard.
- setWantsKeyboardFocus (true);
+ addAndMakeVisible (label);
+ addAndMakeVisible (editor);
}
void resized() override
{
- const int labelColumnWidth = getWidth() / 3;
- #if JUCE_ANDROID && __ANDROID_API__ >= 26
- const int rowHeight = getHeight() / 8;
- #else
- const int rowHeight = getHeight() / 7;
- #endif
-
- auto layoutRow = [labelColumnWidth] (Rectangle rowBounds, Component& label, Component& editor)
- {
- label .setBounds (rowBounds.removeFromLeft (labelColumnWidth));
- editor.setBounds (rowBounds);
- };
-
auto bounds = getLocalBounds();
-
- layoutRow (bounds.removeFromTop (rowHeight), identifierLabel, identifierEditor);
- layoutRow (bounds.removeFromTop (rowHeight), titleLabel, titleEditor);
- layoutRow (bounds.removeFromTop (4 * rowHeight), bodyLabel, bodyEditor);
- #if JUCE_IOS
- layoutRow (bounds.removeFromTop (rowHeight), categoryLabel, categoryComboBox);
- #elif JUCE_ANDROID
- #if __ANDROID_API__ >= 26
- layoutRow (bounds.removeFromTop (rowHeight), channelIdLabel, channelIdComboBox);
- #endif
- layoutRow (bounds.removeFromTop (rowHeight), iconLabel, iconComboBox);
- #endif
+ label .setBounds (bounds.removeFromLeft (getWidth() / 3));
+ editor.setBounds (bounds);
}
- Label identifierLabel { "identifierLabel", "Identifier" };
- TextEditor identifierEditor;
- Label titleLabel { "titleLabel", "Title" };
- TextEditor titleEditor;
- Label bodyLabel { "bodyLabel", "Body" };
- TextEditor bodyEditor;
- #if JUCE_IOS
- StringArray categories;
- Label categoryLabel { "categoryLabel", "Category" };
- ComboBox categoryComboBox;
- #elif JUCE_ANDROID
- Label channelIdLabel { "channelIdLabel", "Channel ID" };
- ComboBox channelIdComboBox;
- Label iconLabel { "iconLabel", "Icon" };
- ComboBox iconComboBox;
- #endif
+
+ Label& label;
+ Component& editor;
+ int rowUnits;
};
- struct OptionalParamsOneView : public Component
+ struct ParamControls
{
- OptionalParamsOneView()
- {
- addAndMakeVisible (subtitleLabel);
- addAndMakeVisible (subtitleEditor);
- addAndMakeVisible (badgeNumberLabel);
- addAndMakeVisible (badgeNumberComboBox);
- addAndMakeVisible (soundToPlayLabel);
- addAndMakeVisible (soundToPlayComboBox);
- addAndMakeVisible (propertiesLabel);
- addAndMakeVisible (propertiesEditor);
- #if JUCE_IOS
- addAndMakeVisible (fireInLabel);
- addAndMakeVisible (fireInComboBox);
- addAndMakeVisible (repeatLabel);
- addAndMakeVisible (repeatButton);
+ Label identifierLabel { "identifierLabel", "Identifier" };
+ TextEditor identifierEditor;
+ Label titleLabel { "titleLabel", "Title" };
+ TextEditor titleEditor;
+ Label bodyLabel { "bodyLabel", "Body" };
+ TextEditor bodyEditor;
- fireInComboBox.addItem ("Now", 1);
+ Label categoryLabel { "categoryLabel", "Category" };
+ ComboBox categoryComboBox;
+ Label channelIdLabel { "channelIdLabel", "Channel ID" };
+ ComboBox channelIdComboBox;
+ Label iconLabel { "iconLabel", "Icon" };
+ ComboBox iconComboBox;
- for (int i = 1; i < 11; ++i)
- fireInComboBox.addItem (String (10 * i) + "seconds", i + 1);
- fireInComboBox.setSelectedItemIndex (0);
-
- #elif JUCE_ANDROID
- addAndMakeVisible (largeIconLabel);
- addAndMakeVisible (largeIconComboBox);
- addAndMakeVisible (badgeIconLabel);
- addAndMakeVisible (badgeIconComboBox);
- addAndMakeVisible (tickerTextLabel);
- addAndMakeVisible (tickerTextEditor);
- addAndMakeVisible (autoCancelLabel);
- addAndMakeVisible (autoCancelButton);
- addAndMakeVisible (alertOnlyOnceLabel);
- addAndMakeVisible (alertOnlyOnceButton);
- addAndMakeVisible (actionsLabel);
- addAndMakeVisible (actionsComboBox);
-
- largeIconComboBox.addItem ("none", 1);
-
- for (int i = 1; i < 5; ++i)
- largeIconComboBox.addItem ("icon" + String (i), i + 1);
- largeIconComboBox.setSelectedItemIndex (0);
-
- badgeIconComboBox.addItem ("none", 1);
- badgeIconComboBox.addItem ("small", 2);
- badgeIconComboBox.addItem ("large", 3);
- badgeIconComboBox.setSelectedItemIndex (2);
-
- actionsComboBox.addItem ("none", 1);
- actionsComboBox.addItem ("ok-cancel", 2);
- actionsComboBox.addItem ("ok-cancel-icons", 3);
- actionsComboBox.addItem ("text-input", 4);
- actionsComboBox.addItem ("text-input-limited_responses", 5);
- actionsComboBox.setSelectedItemIndex (0);
- #endif
-
- for (int i = 0; i < 7; ++i)
- badgeNumberComboBox.addItem (String (i), i + 1);
- badgeNumberComboBox.setSelectedItemIndex (0);
-
- #if JUCE_IOS
- String prefix = "sounds/";
- String extension = ".caf";
- #else
- String prefix;
- String extension;
- #endif
-
- soundToPlayComboBox.addItem ("none", 1);
- soundToPlayComboBox.addItem ("default_os_sound", 2);
- soundToPlayComboBox.addItem (prefix + "demonstrative" + extension, 3);
- soundToPlayComboBox.addItem (prefix + "isntit" + extension, 4);
- soundToPlayComboBox.addItem (prefix + "jinglebellssms" + extension, 5);
- soundToPlayComboBox.addItem (prefix + "served" + extension, 6);
- soundToPlayComboBox.addItem (prefix + "solemn" + extension, 7);
- soundToPlayComboBox.setSelectedItemIndex (1);
-
- // For now, to be able to dismiss mobile keyboard.
- setWantsKeyboardFocus (true);
- }
-
- void resized() override
- {
- const int labelColumnWidth = getWidth() / 3;
- #if JUCE_ANDROID
- const int rowHeight = getHeight() / 12;
- #else
- const int rowHeight = getHeight() / 8;
- #endif
-
- auto layoutRow = [labelColumnWidth] (Rectangle rowBounds, Component& label, Component& editor)
- {
- label .setBounds (rowBounds.removeFromLeft (labelColumnWidth));
- editor.setBounds (rowBounds);
- };
-
- auto bounds = getLocalBounds();
-
- layoutRow (bounds.removeFromTop (rowHeight), subtitleLabel, subtitleEditor);
- layoutRow (bounds.removeFromTop (rowHeight), badgeNumberLabel, badgeNumberComboBox);
- layoutRow (bounds.removeFromTop (rowHeight), soundToPlayLabel, soundToPlayComboBox);
- layoutRow (bounds.removeFromTop (3 * rowHeight), propertiesLabel, propertiesEditor);
- #if JUCE_IOS
- layoutRow (bounds.removeFromTop (rowHeight), fireInLabel, fireInComboBox);
- layoutRow (bounds.removeFromTop (rowHeight), repeatLabel, repeatButton);
- #elif JUCE_ANDROID
- layoutRow (bounds.removeFromTop (rowHeight), largeIconLabel, largeIconComboBox);
- layoutRow (bounds.removeFromTop (rowHeight), badgeIconLabel, badgeIconComboBox);
- layoutRow (bounds.removeFromTop (rowHeight), tickerTextLabel, tickerTextEditor);
- layoutRow (bounds.removeFromTop (rowHeight), autoCancelLabel, autoCancelButton);
- layoutRow (bounds.removeFromTop (rowHeight), alertOnlyOnceLabel, alertOnlyOnceButton);
- layoutRow (bounds.removeFromTop (rowHeight), actionsLabel, actionsComboBox);
- #endif
- }
- Label subtitleLabel { "subtitleLabel", "Subtitle" };
- TextEditor subtitleEditor;
- Label badgeNumberLabel { "badgeNumberLabel", "BadgeNumber" };
- ComboBox badgeNumberComboBox;
- Label soundToPlayLabel { "soundToPlayLabel", "SoundToPlay" };
- ComboBox soundToPlayComboBox;
- Label propertiesLabel { "propertiesLabel", "Properties" };
- TextEditor propertiesEditor;
- #if JUCE_IOS
+ 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;
- #elif JUCE_ANDROID
Label largeIconLabel { "largeIconLabel", "Large Icon" };
ComboBox largeIconComboBox;
Label badgeIconLabel { "badgeIconLabel", "Badge Icon" };
@@ -324,106 +155,6 @@ private:
ToggleButton alertOnlyOnceButton;
Label actionsLabel { "actionsLabel", "Actions" };
ComboBox actionsComboBox;
- #endif
- };
-
- struct OptionalParamsTwoView : public Component
- {
- OptionalParamsTwoView()
- {
- addAndMakeVisible (progressMaxLabel);
- addAndMakeVisible (progressMaxComboBox);
- addAndMakeVisible (progressCurrentLabel);
- addAndMakeVisible (progressCurrentComboBox);
- addAndMakeVisible (progressIndeterminateLabel);
- addAndMakeVisible (progressIndeterminateButton);
- addAndMakeVisible (categoryLabel);
- addAndMakeVisible (categoryComboBox);
- addAndMakeVisible (priorityLabel);
- addAndMakeVisible (priorityComboBox);
- addAndMakeVisible (personLabel);
- addAndMakeVisible (personEditor);
- addAndMakeVisible (lockScreenVisibilityLabel);
- addAndMakeVisible (lockScreenVisibilityComboBox);
- addAndMakeVisible (groupIdLabel);
- addAndMakeVisible (groupIdEditor);
- addAndMakeVisible (sortKeyLabel);
- addAndMakeVisible (sortKeyEditor);
- addAndMakeVisible (groupSummaryLabel);
- addAndMakeVisible (groupSummaryButton);
- addAndMakeVisible (groupAlertBehaviourLabel);
- addAndMakeVisible (groupAlertBehaviourComboBox);
-
- for (int i = 0; i < 11; ++i)
- {
- progressMaxComboBox .addItem (String (i * 10) + "%", i + 1);
- progressCurrentComboBox.addItem (String (i * 10) + "%", i + 1);
- }
-
- progressMaxComboBox .setSelectedItemIndex (0);
- progressCurrentComboBox.setSelectedItemIndex (0);
-
- categoryComboBox.addItem ("unspecified", 1);
- categoryComboBox.addItem ("alarm", 2);
- categoryComboBox.addItem ("call", 3);
- categoryComboBox.addItem ("email", 4);
- categoryComboBox.addItem ("error", 5);
- categoryComboBox.addItem ("event", 6);
- categoryComboBox.addItem ("message", 7);
- categoryComboBox.addItem ("progress", 8);
- categoryComboBox.addItem ("promo", 9);
- categoryComboBox.addItem ("recommendation", 10);
- categoryComboBox.addItem ("reminder", 11);
- categoryComboBox.addItem ("service", 12);
- categoryComboBox.addItem ("social", 13);
- categoryComboBox.addItem ("status", 14);
- categoryComboBox.addItem ("system", 15);
- categoryComboBox.addItem ("transport", 16);
- categoryComboBox.setSelectedItemIndex (0);
-
- for (int i = -2; i < 3; ++i)
- priorityComboBox.addItem (String (i), i + 3);
- priorityComboBox.setSelectedItemIndex (2);
-
- lockScreenVisibilityComboBox.addItem ("don't show", 1);
- lockScreenVisibilityComboBox.addItem ("show partially", 2);
- lockScreenVisibilityComboBox.addItem ("show completely", 3);
- lockScreenVisibilityComboBox.setSelectedItemIndex (1);
-
- groupAlertBehaviourComboBox.addItem ("alert all", 1);
- groupAlertBehaviourComboBox.addItem ("alert summary", 2);
- groupAlertBehaviourComboBox.addItem ("alert children", 3);
- groupAlertBehaviourComboBox.setSelectedItemIndex (0);
-
- // For now, to be able to dismiss mobile keyboard.
- setWantsKeyboardFocus (true);
- }
-
- void resized() override
- {
- const int labelColumnWidth = getWidth() / 3;
- const int rowHeight = getHeight() / 11;
-
- auto layoutRow = [labelColumnWidth] (Rectangle rowBounds, Component& label, Component& editor)
- {
- label .setBounds (rowBounds.removeFromLeft (labelColumnWidth));
- editor.setBounds (rowBounds);
- };
-
- auto bounds = getLocalBounds();
-
- layoutRow (bounds.removeFromTop (rowHeight), progressMaxLabel, progressMaxComboBox);
- layoutRow (bounds.removeFromTop (rowHeight), progressCurrentLabel, progressCurrentComboBox);
- layoutRow (bounds.removeFromTop (rowHeight), progressIndeterminateLabel, progressIndeterminateButton);
- layoutRow (bounds.removeFromTop (rowHeight), categoryLabel, categoryComboBox);
- layoutRow (bounds.removeFromTop (rowHeight), priorityLabel, priorityComboBox);
- layoutRow (bounds.removeFromTop (rowHeight), personLabel, personEditor);
- layoutRow (bounds.removeFromTop (rowHeight), lockScreenVisibilityLabel, lockScreenVisibilityComboBox);
- layoutRow (bounds.removeFromTop (rowHeight), groupIdLabel, groupIdEditor);
- layoutRow (bounds.removeFromTop (rowHeight), sortKeyLabel, sortKeyEditor);
- layoutRow (bounds.removeFromTop (rowHeight), groupSummaryLabel, groupSummaryButton);
- layoutRow (bounds.removeFromTop (rowHeight), groupAlertBehaviourLabel, groupAlertBehaviourComboBox);
- }
Label progressMaxLabel { "progressMaxLabel", "ProgressMax" };
ComboBox progressMaxComboBox;
@@ -431,8 +162,8 @@ private:
ComboBox progressCurrentComboBox;
Label progressIndeterminateLabel { "progressIndeterminateLabel", "ProgressIndeterminate" };
ToggleButton progressIndeterminateButton;
- Label categoryLabel { "categoryLabel", "Category" };
- ComboBox categoryComboBox;
+ Label notifCategoryLabel { "notifCategoryLabel", "Category" };
+ ComboBox notifCategoryComboBox;
Label priorityLabel { "priorityLabel", "Priority" };
ComboBox priorityComboBox;
Label personLabel { "personLabel", "Person" };
@@ -447,84 +178,6 @@ private:
ToggleButton groupSummaryButton;
Label groupAlertBehaviourLabel { "groupAlertBehaviourLabel", "GroupAlertBehaviour" };
ComboBox groupAlertBehaviourComboBox;
- };
-
- struct OptionalParamsThreeView : public Component
- {
- OptionalParamsThreeView()
- {
- addAndMakeVisible (accentColourLabel);
- addAndMakeVisible (accentColourButton);
- addAndMakeVisible (ledColourLabel);
- addAndMakeVisible (ledColourButton);
- addAndMakeVisible (ledMsToBeOnLabel);
- addAndMakeVisible (ledMsToBeOnComboBox);
- addAndMakeVisible (ledMsToBeOffLabel);
- addAndMakeVisible (ledMsToBeOffComboBox);
- addAndMakeVisible (vibratorMsToBeOnLabel);
- addAndMakeVisible (vibratorMsToBeOnComboBox);
- addAndMakeVisible (vibratorMsToBeOffLabel);
- addAndMakeVisible (vibratorMsToBeOffComboBox);
- addAndMakeVisible (localOnlyLabel);
- addAndMakeVisible (localOnlyButton);
- addAndMakeVisible (ongoingLabel);
- addAndMakeVisible (ongoingButton);
- addAndMakeVisible (timestampVisibilityLabel);
- addAndMakeVisible (timestampVisibilityComboBox);
- addAndMakeVisible (timeoutAfterLabel);
- addAndMakeVisible (timeoutAfterComboBox);
-
- timeoutAfterComboBox.addItem ("No timeout", 1);
-
- for (int i = 0; i < 10; ++i)
- {
- ledMsToBeOnComboBox .addItem (String (i * 200) + "ms", i + 1);
- ledMsToBeOffComboBox .addItem (String (i * 200) + "ms", i + 1);
- vibratorMsToBeOnComboBox .addItem (String (i * 500) + "ms", i + 1);
- vibratorMsToBeOffComboBox.addItem (String (i * 500) + "ms", i + 1);
- timeoutAfterComboBox.addItem (String (5000 + 1000 * i) + "ms", i + 2);
- }
-
- ledMsToBeOnComboBox .setSelectedItemIndex (5);
- ledMsToBeOffComboBox .setSelectedItemIndex (5);
- vibratorMsToBeOnComboBox .setSelectedItemIndex (0);
- vibratorMsToBeOffComboBox.setSelectedItemIndex (0);
- timeoutAfterComboBox.setSelectedItemIndex (0);
-
- timestampVisibilityComboBox.addItem ("off", 1);
- timestampVisibilityComboBox.addItem ("on", 2);
- timestampVisibilityComboBox.addItem ("chronometer", 3);
- timestampVisibilityComboBox.addItem ("count down", 4);
- timestampVisibilityComboBox.setSelectedItemIndex (1);
-
- // For now, to be able to dismiss mobile keyboard.
- setWantsKeyboardFocus (true);
- }
-
- void resized() override
- {
- const int labelColumnWidth = getWidth() / 3;
- const int rowHeight = getHeight() / 10;
-
- auto layoutRow = [labelColumnWidth] (Rectangle rowBounds, Component& label, Component& editor)
- {
- label .setBounds (rowBounds.removeFromLeft (labelColumnWidth));
- editor.setBounds (rowBounds);
- };
-
- auto bounds = getLocalBounds();
-
- layoutRow (bounds.removeFromTop (rowHeight), accentColourLabel, accentColourButton);
- layoutRow (bounds.removeFromTop (rowHeight), ledColourLabel, ledColourButton);
- layoutRow (bounds.removeFromTop (rowHeight), ledMsToBeOnLabel, ledMsToBeOnComboBox);
- layoutRow (bounds.removeFromTop (rowHeight), ledMsToBeOffLabel, ledMsToBeOffComboBox);
- layoutRow (bounds.removeFromTop (rowHeight), vibratorMsToBeOnLabel, vibratorMsToBeOnComboBox);
- layoutRow (bounds.removeFromTop (rowHeight), vibratorMsToBeOffLabel, vibratorMsToBeOffComboBox);
- layoutRow (bounds.removeFromTop (rowHeight), localOnlyLabel, localOnlyButton);
- layoutRow (bounds.removeFromTop (rowHeight), ongoingLabel, ongoingButton);
- layoutRow (bounds.removeFromTop (rowHeight), timestampVisibilityLabel, timestampVisibilityComboBox);
- layoutRow (bounds.removeFromTop (rowHeight), timeoutAfterLabel, timeoutAfterComboBox);
- }
Label accentColourLabel { "accentColourLabel", "AccentColour" };
TextButton accentColourButton;
@@ -551,6 +204,45 @@ private:
ColourSelector* ledColourSelector = nullptr;
};
+ void setupControls();
+ void distributeControls();
+
+ 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
+ {
+ int totalRowUnits = 0;
+
+ for (const auto &rc : rowComponents)
+ totalRowUnits += rc->rowUnits;
+
+ const int 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 rowComponents;
+ };
+
struct AuxActionsView : public Component
{
AuxActionsView()
@@ -559,7 +251,7 @@ private:
addAndMakeVisible (removeDeliveredNotifWithIdButton);
addAndMakeVisible (deliveredNotifIdentifier);
addAndMakeVisible (removeAllDeliveredNotifsButton);
- #if JUCE_IOS
+ #if JUCE_IOS || JUCE_MAC
addAndMakeVisible (getPendingNotificationsButton);
addAndMakeVisible (removePendingNotifWithIdButton);
addAndMakeVisible (pendingNotifIdentifier);
@@ -586,7 +278,7 @@ private:
removeAllDeliveredNotifsButton .setBounds (bounds.removeFromTop (rowHeight));
- #if JUCE_IOS
+ #if JUCE_IOS || JUCE_MAC
getPendingNotificationsButton .setBounds (bounds.removeFromTop (rowHeight));
rowBounds = bounds.removeFromTop (rowHeight);
@@ -601,12 +293,10 @@ private:
TextButton removeDeliveredNotifWithIdButton { "Remove Delivered Notif With ID:" };
TextEditor deliveredNotifIdentifier;
TextButton removeAllDeliveredNotifsButton { "Remove All Delivered Notifs" };
- #if JUCE_IOS
TextButton getPendingNotificationsButton { "Get Pending Notifications" };
TextButton removePendingNotifWithIdButton { "Remove Pending Notif With ID:" };
TextEditor pendingNotifIdentifier;
TextButton removeAllPendingNotifsButton { "Remove All Pending Notifs" };
- #endif
};
struct RemoteView : public Component
@@ -641,17 +331,52 @@ private:
TextButton unsubscribeFromSportsButton { "UnsubscribeFromSports" };
};
- Label headerLabel { "headerLabel", "Push Notifications Demo" };
- RequiredParamsView requiredParamsView;
- OptionalParamsOneView optionalParamsOneView;
- OptionalParamsTwoView optionalParamsTwoView;
- OptionalParamsThreeView optionalParamsThreeView;
- AuxActionsView auxActionsView;
- TabbedComponent localNotificationsTabs { TabbedButtonBar::TabsAtTop };
- RemoteView remoteView;
- TabbedComponent mainTabs { TabbedButtonBar::TabsAtTop };
- TextButton sendButton { "Send!" };
- Label notAvailableYetLabel { "notAvailableYetLabel", "Push Notifications feature is not available on this platform yet!" };
+ struct DemoTabbedComponent : public TabbedComponent
+ {
+ explicit DemoTabbedComponent (TabbedButtonBar::Orientation orientation)
+ : TabbedComponent (orientation)
+ {
+ }
+
+ void currentTabChanged (int, const String& newCurrentTabName) override
+ {
+ if (! showedRemoteInstructions && newCurrentTabName == "Remote")
+ {
+ MainContentComponent::showRemoteInstructions();
+
+ showedRemoteInstructions = true;
+ }
+
+ }
+
+ private:
+ bool showedRemoteInstructions = false;
+ };
+
+ static void showRemoteInstructions()
+ {
+ #if JUCE_IOS || JUCE_MAC
+ NativeMessageBox::showMessageBoxAsync (AlertWindow::InfoIcon,
+ "Remote Notifications instructions",
+ "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.");
+ #endif
+ }
+
+ Label headerLabel { "headerLabel", "Push Notifications Demo" };
+ ParamControls paramControls;
+ ParamsView paramsOneView;
+ ParamsView paramsTwoView;
+ ParamsView paramsThreeView;
+ ParamsView 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 (MainContentComponent)
diff --git a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Xcode.h b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Xcode.h
index f55b51c2be..9a6bdbe2b6 100644
--- a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Xcode.h
+++ b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Xcode.h
@@ -249,9 +249,6 @@ public:
props.add (new BooleanPropertyComponent (getBackgroundBleValue(), "Bluetooth MIDI background capability", "Enabled"),
"Enable this to grant your app the capability to connect to Bluetooth LE devices when in background mode.");
- props.add (new BooleanPropertyComponent (getPushNotificationsValue(), "Push Notifications capability", "Enabled"),
- "Enable this to grant your app the capability to receive push notifications.");
-
props.add (new BooleanPropertyComponent (getAppGroupsEnabledValue(), "App groups capability", "Enabled"),
"Enable this to grant your app the capability to share resources between apps using the same app group ID.");
@@ -259,6 +256,9 @@ public:
"Enable this to grant your app the capability to use native file load/save browser windows on iOS.");
}
+ props.add (new BooleanPropertyComponent (getPushNotificationsValue(), "Push Notifications capability", "Enabled"),
+ "Enable this to grant your app the capability to receive push notifications.");
+
props.add (new TextPropertyComponent (getPListToMergeValue(), "Custom PList", 8192, true),
"You can paste the contents of an XML PList file in here, and the settings that it contains will override any "
"settings that the Projucer creates. BEWARE! When doing this, be careful to remove from the XML any "
@@ -860,7 +860,7 @@ public:
&& type == Target::StandalonePlugIn
&& owner.getProject().shouldEnableIAA()) ? 1 : 0;
- auto pushNotificationsEnabled = (owner.iOS && owner.isPushNotificationsEnabled()) ? 1 : 0;
+ auto pushNotificationsEnabled = owner.isPushNotificationsEnabled() ? 1 : 0;
auto sandboxEnabled = (type == Target::AudioUnitv3PlugIn ? 1 : 0);
attributes << "SystemCapabilities = {";
@@ -1146,7 +1146,7 @@ public:
if (owner.isInAppPurchasesEnabled())
defines.set ("JUCE_IN_APP_PURCHASES", "1");
- if (owner.iOS && owner.isPushNotificationsEnabled())
+ if (owner.isPushNotificationsEnabled())
defines.set ("JUCE_PUSH_NOTIFICATIONS", "1");
defines = mergePreprocessorDefs (defines, owner.getAllPreprocessorDefs (config, type));
@@ -2552,8 +2552,10 @@ private:
}
else
{
- if (isiOS() && isPushNotificationsEnabled())
- entitlements.set ("aps-environment", "development");
+ if (isPushNotificationsEnabled())
+ entitlements.set (isiOS() ? "aps-environment"
+ : "com.apple.developer.aps-environment",
+ "development");
}
if (isAppGroupsEnabled())
diff --git a/modules/juce_core/native/juce_osx_ObjCHelpers.h b/modules/juce_core/native/juce_osx_ObjCHelpers.h
index 0702946c29..483ceddb91 100644
--- a/modules/juce_core/native/juce_osx_ObjCHelpers.h
+++ b/modules/juce_core/native/juce_osx_ObjCHelpers.h
@@ -67,6 +67,132 @@ static inline NSArray* createNSArrayFromStringArray (const StringArray& strings)
return [array autorelease];
}
+static NSArray* varArrayToNSArray (const var& varToParse);
+
+static NSDictionary* varObjectToNSDictionary (const var& varToParse)
+{
+ auto* dictionary = [NSMutableDictionary dictionary];
+
+ if (varToParse.isObject())
+ {
+ auto* dynamicObject = varToParse.getDynamicObject();
+
+ auto& properties = dynamicObject->getProperties();
+
+ for (int i = 0; i < properties.size(); ++i)
+ {
+ auto* keyString = juceStringToNS (properties.getName (i).toString());
+
+ const var& valueVar = properties.getValueAt (i);
+
+ if (valueVar.isObject())
+ {
+ auto* valueDictionary = varObjectToNSDictionary (valueVar);
+
+ [dictionary setObject: valueDictionary forKey: keyString];
+ }
+ else if (valueVar.isArray())
+ {
+ auto* valueArray = varArrayToNSArray (valueVar);
+
+ [dictionary setObject: valueArray forKey: keyString];
+ }
+ else
+ {
+ auto* valueString = juceStringToNS (valueVar.toString());
+
+ [dictionary setObject: valueString forKey: keyString];
+ }
+ }
+ }
+
+ return dictionary;
+}
+
+static NSArray* varArrayToNSArray (const var& varToParse)
+{
+ jassert (varToParse.isArray());
+
+ if (! varToParse.isArray())
+ return nil;
+
+ const auto* varArray = varToParse.getArray();
+
+ auto* array = [NSMutableArray arrayWithCapacity: (NSUInteger) varArray->size()];
+
+ for (const auto& aVar : *varArray)
+ {
+ if (aVar.isObject())
+ {
+ auto* valueDictionary = varObjectToNSDictionary (aVar);
+
+ [array addObject: valueDictionary];
+ }
+ else if (aVar.isArray())
+ {
+ auto* valueArray = varArrayToNSArray (aVar);
+
+ [array addObject: valueArray];
+ }
+ else
+ {
+ auto* valueString = juceStringToNS (aVar.toString());
+
+ [array addObject: valueString];
+ }
+ }
+
+ return array;
+}
+
+static var nsArrayToVar (NSArray* array);
+
+static var nsDictionaryToVar (NSDictionary* dictionary)
+{
+ DynamicObject::Ptr dynamicObject = new DynamicObject();
+
+ for (NSString* key in dictionary)
+ {
+ const auto keyString = nsStringToJuce (key);
+
+ id value = dictionary[key];
+
+ if ([value isKindOfClass: [NSString class]])
+ dynamicObject->setProperty (keyString, nsStringToJuce ((NSString*) value));
+ else if ([value isKindOfClass: [NSNumber class]])
+ dynamicObject->setProperty (keyString, nsStringToJuce ([(NSNumber*) value stringValue]));
+ else if ([value isKindOfClass: [NSDictionary class]])
+ dynamicObject->setProperty (keyString, nsDictionaryToVar ((NSDictionary*) value));
+ else if ([value isKindOfClass: [NSArray class]])
+ dynamicObject->setProperty (keyString, nsArrayToVar ((NSArray*) value));
+ else
+ jassertfalse; // Unsupported yet, add here!
+ }
+
+ return var (dynamicObject.get());
+}
+
+static var nsArrayToVar (NSArray* array)
+{
+ Array resultArray;
+
+ for (id value in array)
+ {
+ if ([value isKindOfClass: [NSString class]])
+ resultArray.add (var (nsStringToJuce ((NSString*) value)));
+ else if ([value isKindOfClass: [NSNumber class]])
+ resultArray.add (var (nsStringToJuce ([(NSNumber*) value stringValue])));
+ else if ([value isKindOfClass: [NSDictionary class]])
+ resultArray.add (nsDictionaryToVar ((NSDictionary*) value));
+ else if ([value isKindOfClass: [NSArray class]])
+ resultArray.add (nsArrayToVar ((NSArray*) value));
+ else
+ jassertfalse; // Unsupported yet, add here!
+ }
+
+ return var (resultArray);
+}
+
#if JUCE_MAC
template
static NSRect makeNSRect (const RectangleType& r) noexcept
diff --git a/modules/juce_events/native/juce_mac_MessageManager.mm b/modules/juce_events/native/juce_mac_MessageManager.mm
index 27456e63a9..d6f1eae7dd 100644
--- a/modules/juce_events/native/juce_mac_MessageManager.mm
+++ b/modules/juce_events/native/juce_mac_MessageManager.mm
@@ -102,7 +102,7 @@ private:
{
AppDelegateClass() : ObjCClass ("JUCEAppDelegate_")
{
- addMethod (@selector (applicationWillFinishLaunching:), applicationWillFinishLaunching, "v@:@@");
+ addMethod (@selector (applicationWillFinishLaunching:), applicationWillFinishLaunching, "v@:@");
addMethod (@selector (getUrl:withReplyEvent:), getUrl_withReplyEvent, "v@:@@");
addMethod (@selector (applicationShouldTerminate:), applicationShouldTerminate, "I@:@");
addMethod (@selector (applicationWillTerminate:), applicationWillTerminate, "v@:@");
@@ -116,11 +116,22 @@ private:
addMethod (@selector (mainMenuTrackingEnded:), mainMenuTrackingEnded, "v@:@");
addMethod (@selector (dummyMethod), dummyMethod, "v@:");
+ #if JUCE_PUSH_NOTIFICATIONS
+ //==============================================================================
+ addIvar*> ("pushNotificationsDelegate");
+
+ addMethod (@selector (applicationDidFinishLaunching:), applicationDidFinishLaunching, "v@:@");
+ addMethod (@selector (setPushNotificationsDelegate:), setPushNotificationsDelegate, "v@:@");
+ addMethod (@selector (application:didRegisterForRemoteNotificationsWithDeviceToken:), registeredForRemoteNotifications, "v@:@@");
+ addMethod (@selector (application:didFailToRegisterForRemoteNotificationsWithError:), failedToRegisterForRemoteNotifications, "v@:@@");
+ addMethod (@selector (application:didReceiveRemoteNotification:), didReceiveRemoteNotification, "v@:@@");
+ #endif
+
registerClass();
}
private:
- static void applicationWillFinishLaunching (id self, SEL, NSApplication*, NSNotification*)
+ static void applicationWillFinishLaunching (id self, SEL, NSNotification*)
{
[[NSAppleEventManager sharedAppleEventManager] setEventHandler: self
andSelector: @selector (getUrl:withReplyEvent:)
@@ -128,6 +139,19 @@ private:
andEventID: kAEGetURL];
}
+ #if JUCE_PUSH_NOTIFICATIONS
+ static void applicationDidFinishLaunching (id self, SEL, NSNotification* notification)
+ {
+ if (notification.userInfo != nil)
+ {
+ NSUserNotification* userNotification = [notification.userInfo objectForKey: nsStringLiteral ("NSApplicationLaunchUserNotificationKey")];
+
+ if (userNotification != nil && userNotification.userInfo != nil)
+ didReceiveRemoteNotification (self, nil, [NSApplication sharedApplication], userNotification.userInfo);
+ }
+ }
+ #endif
+
static NSApplicationTerminateReply applicationShouldTerminate (id /*self*/, SEL, NSApplication*)
{
if (auto* app = JUCEApplicationBase::getInstance())
@@ -216,6 +240,73 @@ private:
return s;
}
+
+ #if JUCE_PUSH_NOTIFICATIONS
+ //==============================================================================
+ static void setPushNotificationsDelegate (id self, SEL, NSObject* delegate)
+ {
+ object_setInstanceVariable (self, "pushNotificationsDelegate", delegate);
+ }
+
+ static NSObject* getPushNotificationsDelegate (id self)
+ {
+ return getIvar*> (self, "pushNotificationsDelegate");
+ }
+
+ static void registeredForRemoteNotifications (id self, SEL, NSApplication* application, NSData* deviceToken)
+ {
+ auto* delegate = getPushNotificationsDelegate (self);
+
+ SEL selector = NSSelectorFromString (@"application:didRegisterForRemoteNotificationsWithDeviceToken:");
+
+ if (delegate != nil && [delegate respondsToSelector: selector])
+ {
+ NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]];
+ [invocation setSelector: selector];
+ [invocation setTarget: delegate];
+ [invocation setArgument: &application atIndex:2];
+ [invocation setArgument: &deviceToken atIndex:3];
+
+ [invocation invoke];
+ }
+ }
+
+ static void failedToRegisterForRemoteNotifications (id self, SEL, NSApplication* application, NSError* error)
+ {
+ auto* delegate = getPushNotificationsDelegate (self);
+
+ SEL selector = NSSelectorFromString (@"application:didFailToRegisterForRemoteNotificationsWithError:");
+
+ if (delegate != nil && [delegate respondsToSelector: selector])
+ {
+ NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]];
+ [invocation setSelector: selector];
+ [invocation setTarget: delegate];
+ [invocation setArgument: &application atIndex:2];
+ [invocation setArgument: &error atIndex:3];
+
+ [invocation invoke];
+ }
+ }
+
+ static void didReceiveRemoteNotification (id self, SEL, NSApplication* application, NSDictionary* userInfo)
+ {
+ auto* delegate = getPushNotificationsDelegate (self);
+
+ SEL selector = NSSelectorFromString (@"application:didReceiveRemoteNotification:");
+
+ if (delegate != nil && [delegate respondsToSelector: selector])
+ {
+ NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]];
+ [invocation setSelector: selector];
+ [invocation setTarget: delegate];
+ [invocation setArgument: &application atIndex:2];
+ [invocation setArgument: &userInfo atIndex:3];
+
+ [invocation invoke];
+ }
+ }
+ #endif
};
};
diff --git a/modules/juce_gui_extra/juce_gui_extra.cpp b/modules/juce_gui_extra/juce_gui_extra.cpp
index f8693b5634..49b2d6b422 100644
--- a/modules/juce_gui_extra/juce_gui_extra.cpp
+++ b/modules/juce_gui_extra/juce_gui_extra.cpp
@@ -55,6 +55,12 @@
#import
#import
+ #if JUCE_PUSH_NOTIFICATIONS
+ #import
+
+ #include "native/juce_mac_PushNotifications.cpp"
+ #endif
+
//==============================================================================
#elif JUCE_IOS
#if JUCE_PUSH_NOTIFICATIONS
diff --git a/modules/juce_gui_extra/misc/juce_PushNotifications.cpp b/modules/juce_gui_extra/misc/juce_PushNotifications.cpp
index e0c36e2921..62c4de567c 100644
--- a/modules/juce_gui_extra/misc/juce_PushNotifications.cpp
+++ b/modules/juce_gui_extra/misc/juce_PushNotifications.cpp
@@ -28,7 +28,7 @@ namespace juce
{
//==============================================================================
-#if ! JUCE_ANDROID && ! JUCE_IOS
+#if ! JUCE_ANDROID && ! JUCE_IOS && ! JUCE_MAC
bool PushNotifications::Notification::isValid() const noexcept { return true; }
#endif
@@ -49,7 +49,7 @@ void PushNotifications::removeListener (Listener* l) { listeners.remove (l); }
void PushNotifications::requestPermissionsWithSettings (const PushNotifications::Settings& settings)
{
- #if JUCE_PUSH_NOTIFICATIONS && JUCE_IOS
+ #if JUCE_PUSH_NOTIFICATIONS && (JUCE_IOS || JUCE_MAC)
pimpl->requestPermissionsWithSettings (settings);
#else
ignoreUnused (settings);
@@ -59,7 +59,7 @@ void PushNotifications::requestPermissionsWithSettings (const PushNotifications:
void PushNotifications::requestSettingsUsed()
{
- #if JUCE_PUSH_NOTIFICATIONS && JUCE_IOS
+ #if JUCE_PUSH_NOTIFICATIONS && (JUCE_IOS || JUCE_MAC)
pimpl->requestSettingsUsed();
#else
listeners.call (&PushNotifications::Listener::notificationSettingsReceived, {});
diff --git a/modules/juce_gui_extra/misc/juce_PushNotifications.h b/modules/juce_gui_extra/misc/juce_PushNotifications.h
index 94ee5ccffc..617a54d967 100644
--- a/modules/juce_gui_extra/misc/juce_PushNotifications.h
+++ b/modules/juce_gui_extra/misc/juce_PushNotifications.h
@@ -124,6 +124,14 @@ public:
URL soundToPlay; /**< Optional: empty when the notification should be silent. When the name is set to
"default_os_sound", then a default sound will be used.
+ For a custom sound on OSX, set the URL to the name of a sound file (preferably without
+ an extension) and place the sound file directly in bundle's "Resources" directory (you
+ can use "Xcode Resource" tickbox in Projucer to achieve that), i.e. it cannot be in a
+ subdirectory of "Resources" like "Resources/sound". Alternatively, if a sound file
+ cannot be found in bundle's "Resources" directory, the OS may look for the sound in the
+ following paths: "~/Library/Sounds", "/Library/Sounds", "/Network/Library/Sounds",
+ "/System/Library/Sounds".
+
For a custom sound on iOS, set the URL to a relative path within your bundle, including
file extension. For instance, if your bundle contains "sounds" folder with "my_sound.caf"
file, then the URL should be "sounds/my_sound.caf".
@@ -312,7 +320,11 @@ public:
//==========================================================================
/** Describes settings we want to use for current device. Note that at the
- moment this is only used on iOS.
+ moment this is only used on iOS and partially on OSX.
+
+ On OSX only allow* flags are used and they control remote notifications only.
+ To control sound, alert and badge settings for local notifications on OSX,
+ use Notifications settings in System Preferences.
To setup push notifications for current device, provide permissions required,
as well as register categories of notifications you want to support. Each
@@ -411,9 +423,13 @@ public:
on user's subsequent changes in OS settings, the actual current settings may be
different (e.g. user might have later decided to disable sounds).
- Note that settings are currently only used on iOS. When calling on other platforms, Settings
- with no categories and all allow* flags set to true will be received in
- Listener::notificationSettingsReceived().
+ Note that settings are currently only used on iOS and partially on OSX.
+
+ On OSX, only allow* flags are used and they refer to remote notifications only. For
+ local notifications, refer to System Preferences.
+
+ When calling this function on other platforms, Settings with no categories and all allow*
+ flags set to true will be received in Listener::notificationSettingsReceived().
*/
void requestSettingsUsed();
@@ -486,7 +502,7 @@ public:
//==========================================================================
/** Checks whether notifications are enabled for given application.
- On iOS this will always return true, use requestSettingsUsed() instead.
+ On iOS and OSX this will always return true, use requestSettingsUsed() instead.
*/
bool areNotificationsEnabled() const;
diff --git a/modules/juce_gui_extra/native/juce_ios_PushNotifications.cpp b/modules/juce_gui_extra/native/juce_ios_PushNotifications.cpp
index 59612650f6..74fdd65ff3 100644
--- a/modules/juce_gui_extra/native/juce_ios_PushNotifications.cpp
+++ b/modules/juce_gui_extra/native/juce_ios_PushNotifications.cpp
@@ -34,84 +34,6 @@ template <> struct ContainerDeletePolicygetProperties();
-
- for (int i = 0; i < properties.size(); ++i)
- {
- NSString* keyString = juceStringToNS (properties.getName (i).toString());
-
- const var& valueVar = properties.getValueAt (i);
-
- if (valueVar.isObject())
- {
- NSDictionary* valueDictionary = varObjectToNSDictionary (valueVar);
-
- [dictionary setObject: valueDictionary forKey: keyString];
- }
- else if (valueVar.isArray())
- {
- NSArray* valueArray = varArrayToNSArray (valueVar);
-
- [dictionary setObject: valueArray forKey: keyString];
- }
- else
- {
- NSString* valueString = juceStringToNS (valueVar.toString());
-
- [dictionary setObject: valueString forKey: keyString];
- }
- }
- }
-
- return dictionary;
- }
-
- NSArray* varArrayToNSArray (const var& varToParse)
- {
- jassert (varToParse.isArray());
-
- if (! varToParse.isArray())
- return nil;
-
- const auto* varArray = varToParse.getArray();
-
- NSMutableArray* array = [NSMutableArray arrayWithCapacity: (NSUInteger) varArray->size()];
-
- for (const auto& aVar : *varArray)
- {
- if (aVar.isObject())
- {
- NSDictionary* valueDictionary = varObjectToNSDictionary (aVar);
-
- [array addObject: valueDictionary];
- }
- else if (aVar.isArray())
- {
- NSArray* valueArray = varArrayToNSArray (aVar);
-
- [array addObject: valueArray];
- }
- else
- {
- NSString* valueString = juceStringToNS (aVar.toString());
-
- [array addObject: valueString];
- }
- }
-
- return array;
- }
-
using Action = PushNotifications::Settings::Action;
using Category = PushNotifications::Settings::Category;
@@ -210,7 +132,7 @@ namespace PushNotificationsDelegateDetails
auto triggerTime = Time::getCurrentTime() + RelativeTime (n.triggerIntervalSec);
notification.fireDate = [NSDate dateWithTimeIntervalSince1970: triggerTime.toMilliseconds() / 1000.];
- notification.userInfo = PushNotificationsDelegateDetails::varObjectToNSDictionary (n.properties);
+ notification.userInfo = varObjectToNSDictionary (n.properties);
auto soundToPlayString = n.soundToPlay.toString (true);
@@ -242,7 +164,7 @@ namespace PushNotificationsDelegateDetails
else if (soundToPlayString.isNotEmpty())
content.sound = [UNNotificationSound soundNamed: juceStringToNS (soundToPlayString)];
- NSMutableDictionary* propsDict = (NSMutableDictionary*) PushNotificationsDelegateDetails::varObjectToNSDictionary (n.properties);
+ NSMutableDictionary* propsDict = (NSMutableDictionary*) varObjectToNSDictionary (n.properties);
[propsDict setObject: juceStringToNS (soundToPlayString) forKey: nsStringLiteral ("com.juce.soundName")];
content.userInfo = propsDict;
@@ -268,54 +190,6 @@ namespace PushNotificationsDelegateDetails
}
#endif
- var nsArrayToVar (NSArray* array);
-
- var nsDictionaryToVar (NSDictionary* dictionary)
- {
- DynamicObject::Ptr dynamicObject = new DynamicObject();
-
- for (NSString* key in dictionary)
- {
- const auto keyString = nsStringToJuce (key);
-
- id value = dictionary[key];
-
- if ([value isKindOfClass: [NSString class]])
- dynamicObject->setProperty (keyString, nsStringToJuce ((NSString*) value));
- else if ([value isKindOfClass: [NSNumber class]])
- dynamicObject->setProperty (keyString, nsStringToJuce ([(NSNumber*) value stringValue]));
- else if ([value isKindOfClass: [NSDictionary class]])
- dynamicObject->setProperty (keyString, nsDictionaryToVar ((NSDictionary*) value));
- else if ([value isKindOfClass: [NSArray class]])
- dynamicObject->setProperty (keyString, nsArrayToVar ((NSArray*) value));
- else
- jassertfalse; // Unsupported yet, add here!
- }
-
- return var (dynamicObject);
- }
-
- var nsArrayToVar (NSArray* array)
- {
- Array resultArray;
-
- for (id value in array)
- {
- if ([value isKindOfClass: [NSString class]])
- resultArray.add (var (nsStringToJuce ((NSString*) value)));
- else if ([value isKindOfClass: [NSNumber class]])
- resultArray.add (var (nsStringToJuce ([(NSNumber*) value stringValue])));
- else if ([value isKindOfClass: [NSDictionary class]])
- resultArray.add (nsDictionaryToVar ((NSDictionary*) value));
- else if ([value isKindOfClass: [NSArray class]])
- resultArray.add (nsArrayToVar ((NSArray*) value));
- else
- jassertfalse; // Unsupported yet, add here!
- }
-
- return var (resultArray);
- }
-
String getUserResponseFromNSDictionary (NSDictionary* dictionary)
{
if (dictionary == nil || dictionary.count == 0)
@@ -397,7 +271,7 @@ namespace PushNotificationsDelegateDetails
n.category = nsStringToJuce (r.content.categoryIdentifier);
n.badgeNumber = r.content.badge.intValue;
- auto userInfoVar = PushNotificationsDelegateDetails::nsDictionaryToVar (r.content.userInfo);
+ auto userInfoVar = nsDictionaryToVar (r.content.userInfo);
if (auto* object = userInfoVar.getDynamicObject())
{
@@ -755,8 +629,6 @@ struct PushNotifications::Pimpl : private PushNotificationsDelegate
#endif
[[UIApplication sharedApplication] registerForRemoteNotifications];
-
- initialised = true;
}
void requestSettingsUsed()
@@ -980,6 +852,8 @@ struct PushNotifications::Pimpl : private PushNotificationsDelegate
deviceToken = nsStringToJuce (deviceTokenString);
+ initialised = true;
+
owner.listeners.call (&PushNotifications::Listener::deviceTokenRefreshed, deviceToken);
}
diff --git a/modules/juce_gui_extra/native/juce_mac_PushNotifications.cpp b/modules/juce_gui_extra/native/juce_mac_PushNotifications.cpp
new file mode 100644
index 0000000000..47a9a1df9f
--- /dev/null
+++ b/modules/juce_gui_extra/native/juce_mac_PushNotifications.cpp
@@ -0,0 +1,557 @@
+/*
+ ==============================================================================
+
+ This file is part of the JUCE library.
+ Copyright (c) 2017 - ROLI Ltd.
+
+ JUCE is an open source library subject to commercial or open-source
+ licensing.
+
+ By using JUCE, you agree to the terms of both the JUCE 5 End-User License
+ Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
+ 27th April 2017).
+
+ End User License Agreement: www.juce.com/juce-5-licence
+ Privacy Policy: www.juce.com/juce-5-privacy-policy
+
+ Or: You may also use this code under the terms of the GPL v3 (see
+ www.gnu.org/licenses).
+
+ JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
+ EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
+ DISCLAIMED.
+
+ ==============================================================================
+*/
+
+namespace juce
+{
+
+template <> struct ContainerDeletePolicy> { static void destroy (NSObject* o) { [o release]; } };
+
+namespace PushNotificationsDelegateDetailsOsx
+{
+ using Action = PushNotifications::Notification::Action;
+
+ //==============================================================================
+ NSUserNotification* juceNotificationToNSUserNotification (const PushNotifications::Notification& n,
+ bool isEarlierThanMavericks,
+ bool isEarlierThanYosemite)
+ {
+ auto* notification = [[NSUserNotification alloc] init];
+
+ notification.title = juceStringToNS (n.title);
+ notification.subtitle = juceStringToNS (n.subtitle);
+ notification.informativeText = juceStringToNS (n.body);
+ notification.userInfo = varObjectToNSDictionary (n.properties);
+
+ auto triggerTime = Time::getCurrentTime() + RelativeTime (n.triggerIntervalSec);
+ notification.deliveryDate = [NSDate dateWithTimeIntervalSince1970: triggerTime.toMilliseconds() / 1000.];
+
+ if (n.repeat && n.triggerIntervalSec >= 60)
+ {
+ auto* dateComponents = [[NSDateComponents alloc] init];
+ auto intervalSec = NSInteger (n.triggerIntervalSec);
+ dateComponents.second = intervalSec;
+ dateComponents.nanosecond = NSInteger ((n.triggerIntervalSec - intervalSec) * 1000000000);
+
+ notification.deliveryRepeatInterval = dateComponents;
+
+ [dateComponents autorelease];
+ }
+
+ auto soundToPlayString = n.soundToPlay.toString (true);
+
+ if (soundToPlayString == "default_os_sound")
+ {
+ notification.soundName = NSUserNotificationDefaultSoundName;
+ }
+ else if (soundToPlayString.isNotEmpty())
+ {
+ auto* soundName = juceStringToNS (soundToPlayString.fromLastOccurrenceOf ("/", false, false)
+ .upToLastOccurrenceOf (".", false, false));
+
+ notification.soundName = soundName;
+ }
+
+ notification.hasActionButton = n.actions.size() > 0;
+
+ if (n.actions.size() > 0)
+ notification.actionButtonTitle = juceStringToNS (n.actions.getReference (0).title);
+
+ if (! isEarlierThanMavericks)
+ {
+ notification.identifier = juceStringToNS (n.identifier);
+
+ if (n.actions.size() > 0)
+ {
+ notification.hasReplyButton = n.actions.getReference (0).style == Action::text;
+ notification.responsePlaceholder = juceStringToNS (n.actions.getReference (0).textInputPlaceholder);
+ }
+
+ auto* imageDirectory = n.icon.contains ("/")
+ ? juceStringToNS (n.icon.upToLastOccurrenceOf ("/", false, true))
+ : [NSString string];
+
+ auto* imageName = juceStringToNS (n.icon.fromLastOccurrenceOf ("/", false, false)
+ .upToLastOccurrenceOf (".", false, false));
+ auto* imageExtension = juceStringToNS (n.icon.fromLastOccurrenceOf (".", false, false));
+
+ NSString* imagePath = nil;
+
+ if ([imageDirectory length] == NSUInteger (0))
+ {
+ imagePath = [[NSBundle mainBundle] pathForResource: imageName
+ ofType: imageExtension];
+ }
+ else
+ {
+ imagePath = [[NSBundle mainBundle] pathForResource: imageName
+ ofType: imageExtension
+ inDirectory: imageDirectory];
+ }
+
+ notification.contentImage = [[NSImage alloc] initWithContentsOfFile: imagePath];
+
+ if (! isEarlierThanYosemite)
+ {
+ if (n.actions.size() > 1)
+ {
+ auto* additionalActions = [NSMutableArray arrayWithCapacity: (NSUInteger) n.actions.size() - 1];
+
+ for (int a = 1; a < n.actions.size(); ++a)
+ [additionalActions addObject: [NSUserNotificationAction actionWithIdentifier: juceStringToNS (n.actions[a].identifier)
+ title: juceStringToNS (n.actions[a].title)]];
+
+ notification.additionalActions = additionalActions;
+ }
+ }
+ }
+
+ [notification autorelease];
+
+ return notification;
+ }
+
+ //==============================================================================
+ PushNotifications::Notification nsUserNotificationToJuceNotification (NSUserNotification* n,
+ bool isEarlierThanMavericks,
+ bool isEarlierThanYosemite)
+ {
+ PushNotifications::Notification notif;
+
+ notif.title = nsStringToJuce (n.title);
+ notif.subtitle = nsStringToJuce (n.subtitle);
+ notif.body = nsStringToJuce (n.informativeText);
+
+ notif.repeat = n.deliveryRepeatInterval != nil;
+
+ if (n.deliveryRepeatInterval != nil)
+ {
+ notif.triggerIntervalSec = n.deliveryRepeatInterval.second + (n.deliveryRepeatInterval.nanosecond / 1000000000.);
+ }
+ else
+ {
+ NSDate* dateNow = [NSDate date];
+ notif.triggerIntervalSec = [dateNow timeIntervalSinceDate: n.deliveryDate];
+ }
+
+ notif.soundToPlay = URL (nsStringToJuce (n.soundName));
+ notif.properties = nsDictionaryToVar (n.userInfo);
+
+ if (! isEarlierThanMavericks)
+ {
+ notif.identifier = nsStringToJuce (n.identifier);
+
+ if (n.contentImage != nil)
+ notif.icon = nsStringToJuce ([n.contentImage name]);
+ }
+
+ Array actions;
+
+ if (n.actionButtonTitle != nil)
+ {
+ Action action;
+ action.title = nsStringToJuce (n.actionButtonTitle);
+
+ if (! isEarlierThanMavericks)
+ {
+ if (n.hasReplyButton)
+ action.style = Action::text;
+
+ if (n.responsePlaceholder != nil)
+ action.textInputPlaceholder = nsStringToJuce (n.responsePlaceholder);
+ }
+
+ actions.add (action);
+ }
+
+ if (! isEarlierThanYosemite)
+ {
+ if (n.additionalActions != nil)
+ {
+ for (NSUserNotificationAction* a in n.additionalActions)
+ {
+ Action action;
+ action.identifier = nsStringToJuce (a.identifier);
+ action.title = nsStringToJuce (a.title);
+
+ actions.add (action);
+ }
+ }
+ }
+
+ return notif;
+ }
+
+ //==============================================================================
+ var getNotificationPropertiesFromDictionaryVar (const var& dictionaryVar)
+ {
+ auto* dictionaryVarObject = dictionaryVar.getDynamicObject();
+
+ if (dictionaryVarObject == nullptr)
+ return {};
+
+ const auto& properties = dictionaryVarObject->getProperties();
+
+ DynamicObject::Ptr propsVarObject = new DynamicObject();
+
+ for (int i = 0; i < properties.size(); ++i)
+ {
+ auto propertyName = properties.getName (i).toString();
+
+ if (propertyName == "aps")
+ continue;
+
+ propsVarObject->setProperty (propertyName, properties.getValueAt (i));
+ }
+
+ return var (propsVarObject.get());
+ }
+
+ PushNotifications::Notification nsDictionaryToJuceNotification (NSDictionary* dictionary)
+ {
+ const var dictionaryVar = nsDictionaryToVar (dictionary);
+
+ const var apsVar = dictionaryVar.getProperty ("aps", {});
+
+ if (! apsVar.isObject())
+ return {};
+
+ var alertVar = apsVar.getProperty ("alert", {});
+
+ const var titleVar = alertVar.getProperty ("title", {});
+ const var bodyVar = alertVar.isObject() ? alertVar.getProperty ("body", {}) : alertVar;
+
+ const var categoryVar = apsVar.getProperty ("category", {});
+ const var soundVar = apsVar.getProperty ("sound", {});
+ const var badgeVar = apsVar.getProperty ("badge", {});
+ const var threadIdVar = apsVar.getProperty ("thread-id", {});
+
+ PushNotifications::Notification notification;
+
+ notification.title = titleVar .toString();
+ notification.body = bodyVar .toString();
+ notification.groupId = threadIdVar.toString();
+ notification.category = categoryVar.toString();
+ notification.soundToPlay = URL (soundVar.toString());
+ notification.badgeNumber = (int) badgeVar;
+ notification.properties = getNotificationPropertiesFromDictionaryVar (dictionaryVar);
+
+ return notification;
+ }
+}
+
+//==============================================================================
+struct PushNotificationsDelegate
+{
+ PushNotificationsDelegate() : delegate ([getClass().createInstance() init])
+ {
+ Class::setThis (delegate, this);
+
+ id appDelegate = [[NSApplication sharedApplication] delegate];
+
+ SEL selector = NSSelectorFromString (@"setPushNotificationsDelegate:");
+
+ if ([appDelegate respondsToSelector: selector])
+ [appDelegate performSelector: selector withObject: delegate];
+
+ [NSUserNotificationCenter defaultUserNotificationCenter].delegate = delegate;
+ }
+
+ virtual ~PushNotificationsDelegate()
+ {
+ [NSUserNotificationCenter defaultUserNotificationCenter].delegate = nil;
+ }
+
+ virtual void registeredForRemoteNotifications (NSData* deviceToken) = 0;
+
+ virtual void failedToRegisterForRemoteNotifications (NSError* error) = 0;
+
+ virtual void didReceiveRemoteNotification (NSDictionary* userInfo) = 0;
+
+ virtual void didDeliverNotification (NSUserNotification* notification) = 0;
+
+ virtual void didActivateNotification (NSUserNotification* notification) = 0;
+
+ virtual bool shouldPresentNotification (NSUserNotification* notification) = 0;
+
+protected:
+ ScopedPointer> delegate;
+
+private:
+ struct Class : public ObjCClass>
+ {
+ Class() : ObjCClass> ("JucePushNotificationsDelegate_")
+ {
+ addIvar ("self");
+
+ addMethod (@selector (application:didRegisterForRemoteNotificationsWithDeviceToken:), registeredForRemoteNotifications, "v@:@@");
+ addMethod (@selector (application:didFailToRegisterForRemoteNotificationsWithError:), failedToRegisterForRemoteNotifications, "v@:@@");
+ addMethod (@selector (application:didReceiveRemoteNotification:), didReceiveRemoteNotification, "v@:@@");
+ addMethod (@selector (userNotificationCenter:didDeliverNotification:), didDeliverNotification, "v@:@@");
+ addMethod (@selector (userNotificationCenter:didActivateNotification:), didActivateNotification, "v@:@@");
+ addMethod (@selector (userNotificationCenter:shouldPresentNotification:), shouldPresentNotification, "B@:@@");
+
+ registerClass();
+ }
+
+ //==============================================================================
+ static PushNotificationsDelegate& getThis (id self) { return *getIvar (self, "self"); }
+ static void setThis (id self, PushNotificationsDelegate* d) { object_setInstanceVariable (self, "self", d); }
+
+ //==============================================================================
+ static void registeredForRemoteNotifications (id self, SEL, NSApplication*,
+ NSData* deviceToken) { getThis (self).registeredForRemoteNotifications (deviceToken); }
+
+ static void failedToRegisterForRemoteNotifications (id self, SEL, NSApplication*,
+ NSError* error) { getThis (self).failedToRegisterForRemoteNotifications (error); }
+
+ static void didReceiveRemoteNotification (id self, SEL, NSApplication*,
+ NSDictionary* userInfo) { getThis (self).didReceiveRemoteNotification (userInfo); }
+
+ static void didDeliverNotification (id self, SEL, NSUserNotificationCenter*,
+ NSUserNotification* notification) { getThis (self).didDeliverNotification (notification); }
+
+ static void didActivateNotification (id self, SEL, NSUserNotificationCenter*,
+ NSUserNotification* notification) { getThis (self).didActivateNotification (notification); }
+
+ static bool shouldPresentNotification (id self, SEL, NSUserNotificationCenter*,
+ NSUserNotification* notification) { return getThis (self).shouldPresentNotification (notification); }
+ };
+
+ //==============================================================================
+ static Class& getClass()
+ {
+ static Class c;
+ return c;
+ }
+};
+
+//==============================================================================
+bool PushNotifications::Notification::isValid() const noexcept { return true; }
+
+//==============================================================================
+struct PushNotifications::Pimpl : private PushNotificationsDelegate
+{
+ Pimpl (PushNotifications& p)
+ : owner (p)
+ {
+ }
+
+ void requestPermissionsWithSettings (const PushNotifications::Settings& settingsToUse)
+ {
+ if (isEarlierThanLion)
+ return;
+
+ settings = settingsToUse;
+
+ NSRemoteNotificationType types = NSUInteger ((bool) settings.allowBadge);
+
+ if (isAtLeastMountainLion)
+ types |= ((bool) settings.allowSound << 1 | (bool) settings.allowAlert << 2);
+
+ [[NSApplication sharedApplication] registerForRemoteNotificationTypes: types];
+ }
+
+ void requestSettingsUsed()
+ {
+ if (isEarlierThanLion)
+ {
+ // no settings available
+ owner.listeners.call (&PushNotifications::Listener::notificationSettingsReceived, {});
+ return;
+ }
+
+ settings.allowBadge = [NSApplication sharedApplication].enabledRemoteNotificationTypes & NSRemoteNotificationTypeBadge;
+
+ if (isAtLeastMountainLion)
+ {
+ settings.allowSound = [NSApplication sharedApplication].enabledRemoteNotificationTypes & NSRemoteNotificationTypeSound;
+ settings.allowAlert = [NSApplication sharedApplication].enabledRemoteNotificationTypes & NSRemoteNotificationTypeAlert;
+ }
+
+ owner.listeners.call (&PushNotifications::Listener::notificationSettingsReceived, settings);
+ }
+
+ bool areNotificationsEnabled() const { return true; }
+
+ void sendLocalNotification (const Notification& n)
+ {
+ auto* notification = PushNotificationsDelegateDetailsOsx::juceNotificationToNSUserNotification (n, isEarlierThanMavericks, isEarlierThanYosemite);
+
+ [[NSUserNotificationCenter defaultUserNotificationCenter] scheduleNotification: notification];
+ }
+
+ void getDeliveredNotifications() const
+ {
+ Array notifs;
+
+ for (NSUserNotification* n in [NSUserNotificationCenter defaultUserNotificationCenter].deliveredNotifications)
+ notifs.add (PushNotificationsDelegateDetailsOsx::nsUserNotificationToJuceNotification (n, isEarlierThanMavericks, isEarlierThanYosemite));
+
+ owner.listeners.call (&Listener::deliveredNotificationsListReceived, notifs);
+ }
+
+ void removeAllDeliveredNotifications()
+ {
+ [[NSUserNotificationCenter defaultUserNotificationCenter] removeAllDeliveredNotifications];
+ }
+
+ void removeDeliveredNotification (const String& identifier)
+ {
+ PushNotifications::Notification n;
+ n.identifier = identifier;
+
+ auto nsNotification = PushNotificationsDelegateDetailsOsx::juceNotificationToNSUserNotification (n, isEarlierThanMavericks, isEarlierThanYosemite);
+
+ [[NSUserNotificationCenter defaultUserNotificationCenter] removeDeliveredNotification: nsNotification];
+ }
+
+ void setupChannels (const Array& groups, const Array& channels)
+ {
+ ignoreUnused (groups, channels);
+ }
+
+ void getPendingLocalNotifications() const
+ {
+ Array notifs;
+
+ for (NSUserNotification* n in [NSUserNotificationCenter defaultUserNotificationCenter].scheduledNotifications)
+ notifs.add (PushNotificationsDelegateDetailsOsx::nsUserNotificationToJuceNotification (n, isEarlierThanMavericks, isEarlierThanYosemite));
+
+ owner.listeners.call (&PushNotifications::Listener::pendingLocalNotificationsListReceived, notifs);
+ }
+
+ void removePendingLocalNotification (const String& identifier)
+ {
+ PushNotifications::Notification n;
+ n.identifier = identifier;
+
+ auto nsNotification = PushNotificationsDelegateDetailsOsx::juceNotificationToNSUserNotification (n, isEarlierThanMavericks, isEarlierThanYosemite);
+
+ [[NSUserNotificationCenter defaultUserNotificationCenter] removeScheduledNotification: nsNotification];
+ }
+
+ void removeAllPendingLocalNotifications()
+ {
+ for (NSUserNotification* n in [NSUserNotificationCenter defaultUserNotificationCenter].scheduledNotifications)
+ [[NSUserNotificationCenter defaultUserNotificationCenter] removeScheduledNotification: n];
+ }
+
+ String getDeviceToken()
+ {
+ // You need to call requestPermissionsWithSettings() first.
+ jassert (initialised);
+
+ return deviceToken;
+ }
+
+ //==============================================================================
+ //PushNotificationsDelegate
+ void registeredForRemoteNotifications (NSData* deviceTokenToUse) override
+ {
+ auto* deviceTokenString = [[[[deviceTokenToUse description]
+ stringByReplacingOccurrencesOfString: nsStringLiteral ("<") withString: nsStringLiteral ("")]
+ stringByReplacingOccurrencesOfString: nsStringLiteral (">") withString: nsStringLiteral ("")]
+ stringByReplacingOccurrencesOfString: nsStringLiteral (" ") withString: nsStringLiteral ("")];
+
+ deviceToken = nsStringToJuce (deviceTokenString);
+
+ initialised = true;
+
+ owner.listeners.call (&PushNotifications::Listener::deviceTokenRefreshed, deviceToken);
+ }
+
+ void failedToRegisterForRemoteNotifications (NSError* error) override
+ {
+ ignoreUnused (error);
+
+ deviceToken.clear();
+ }
+
+ void didReceiveRemoteNotification (NSDictionary* userInfo) override
+ {
+ auto n = PushNotificationsDelegateDetailsOsx::nsDictionaryToJuceNotification (userInfo);
+
+ owner.listeners.call (&PushNotifications::Listener::handleNotification, true, n);
+ }
+
+ void didDeliverNotification (NSUserNotification* notification) override
+ {
+ ignoreUnused (notification);
+ }
+
+ void didActivateNotification (NSUserNotification* notification) override
+ {
+ auto n = PushNotificationsDelegateDetailsOsx::nsUserNotificationToJuceNotification (notification, isEarlierThanMavericks, isEarlierThanYosemite);
+
+ if (notification.activationType == NSUserNotificationActivationTypeContentsClicked)
+ {
+ owner.listeners.call (&PushNotifications::Listener::handleNotification, notification.remote, n);
+ }
+ else
+ {
+ auto actionIdentifier = (! isEarlierThanYosemite && notification.additionalActivationAction != nil)
+ ? nsStringToJuce (notification.additionalActivationAction.identifier)
+ : nsStringToJuce (notification.actionButtonTitle);
+
+ auto reply = notification.activationType == NSUserNotificationActivationTypeReplied
+ ? nsStringToJuce ([notification.response string])
+ : String();
+
+ owner.listeners.call (&PushNotifications::Listener::handleNotificationAction, notification.remote, n, actionIdentifier, reply);
+ }
+ }
+
+ bool shouldPresentNotification (NSUserNotification* notification) override { return true; }
+
+ void subscribeToTopic (const String& topic) { ignoreUnused (topic); }
+ void unsubscribeFromTopic (const String& topic) { ignoreUnused (topic); }
+
+ void sendUpstreamMessage (const String& serverSenderId,
+ const String& collapseKey,
+ const String& messageId,
+ const String& messageType,
+ int timeToLive,
+ const StringPairArray& additionalData)
+ {
+ ignoreUnused (serverSenderId, collapseKey, messageId, messageType);
+ ignoreUnused (timeToLive, additionalData);
+ }
+
+private:
+ PushNotifications& owner;
+
+ const bool isEarlierThanLion = std::floor (NSFoundationVersionNumber) < std::floor (NSFoundationVersionNumber10_7);
+ const bool isAtLeastMountainLion = std::floor (NSFoundationVersionNumber) >= NSFoundationVersionNumber10_7;
+ const bool isEarlierThanMavericks = std::floor (NSFoundationVersionNumber) < NSFoundationVersionNumber10_9;
+ const bool isEarlierThanYosemite = std::floor (NSFoundationVersionNumber) <= NSFoundationVersionNumber10_9;
+
+ bool initialised = false;
+ String deviceToken;
+
+ PushNotifications::Settings settings;
+};
+
+} // namespace juce