Browse Source

PushNotifications: add OSX implementation.

tags/2021-05-28
Lukasz Kozakiewicz 8 years ago
parent
commit
b8b304e4cd
11 changed files with 1311 additions and 692 deletions
  1. +17
    -11
      examples/PushNotificationsDemo/PushNotificationsDemo.jucer
  2. +347
    -131
      examples/PushNotificationsDemo/Source/MainComponent.cpp
  3. +127
    -402
      examples/PushNotificationsDemo/Source/MainComponent.h
  4. +9
    -7
      extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Xcode.h
  5. +126
    -0
      modules/juce_core/native/juce_osx_ObjCHelpers.h
  6. +93
    -2
      modules/juce_events/native/juce_mac_MessageManager.mm
  7. +6
    -0
      modules/juce_gui_extra/juce_gui_extra.cpp
  8. +3
    -3
      modules/juce_gui_extra/misc/juce_PushNotifications.cpp
  9. +21
    -5
      modules/juce_gui_extra/misc/juce_PushNotifications.h
  10. +5
    -131
      modules/juce_gui_extra/native/juce_ios_PushNotifications.cpp
  11. +557
    -0
      modules/juce_gui_extra/native/juce_mac_PushNotifications.cpp

+ 17
- 11
examples/PushNotificationsDemo/PushNotificationsDemo.jucer View File

@@ -31,19 +31,25 @@
</GROUP>
<GROUP id="{D7B3F6B4-2E9A-E281-831A-2B67D6D4C4A0}" name="sounds">
<FILE id="il8LpK" name="demonstrative.caf" compile="0" resource="1"
file="BinaryResources/sounds/demonstrative.caf"/>
file="BinaryResources/sounds/demonstrative.caf" xcodeResource="0"/>
<FILE id="aUWCPt" name="demonstrative.mp3" compile="0" resource="1"
file="BinaryResources/sounds/demonstrative.mp3"/>
<FILE id="PWlDpi" name="isntit.caf" compile="0" resource="1" file="BinaryResources/sounds/isntit.caf"/>
<FILE id="nbNoVQ" name="isntit.mp3" compile="0" resource="1" file="BinaryResources/sounds/isntit.mp3"/>
file="BinaryResources/sounds/demonstrative.mp3" xcodeResource="1"/>
<FILE id="PWlDpi" name="isntit.caf" compile="0" resource="1" file="BinaryResources/sounds/isntit.caf"
xcodeResource="0"/>
<FILE id="nbNoVQ" name="isntit.mp3" compile="0" resource="1" file="BinaryResources/sounds/isntit.mp3"
xcodeResource="1"/>
<FILE id="AheEYO" name="jinglebellssms.caf" compile="0" resource="1"
file="BinaryResources/sounds/jinglebellssms.caf"/>
file="BinaryResources/sounds/jinglebellssms.caf" xcodeResource="0"/>
<FILE id="WeQemY" name="jinglebellssms.mp3" compile="0" resource="1"
file="BinaryResources/sounds/jinglebellssms.mp3"/>
<FILE id="TmLrYM" name="served.caf" compile="0" resource="1" file="BinaryResources/sounds/served.caf"/>
<FILE id="Ad45fH" name="served.mp3" compile="0" resource="1" file="BinaryResources/sounds/served.mp3"/>
<FILE id="qH4XUR" name="solemn.caf" compile="0" resource="1" file="BinaryResources/sounds/solemn.caf"/>
<FILE id="cwrEXS" name="solemn.mp3" compile="0" resource="1" file="BinaryResources/sounds/solemn.mp3"/>
file="BinaryResources/sounds/jinglebellssms.mp3" xcodeResource="1"/>
<FILE id="TmLrYM" name="served.caf" compile="0" resource="1" file="BinaryResources/sounds/served.caf"
xcodeResource="0"/>
<FILE id="Ad45fH" name="served.mp3" compile="0" resource="1" file="BinaryResources/sounds/served.mp3"
xcodeResource="1"/>
<FILE id="qH4XUR" name="solemn.caf" compile="0" resource="1" file="BinaryResources/sounds/solemn.caf"
xcodeResource="0"/>
<FILE id="cwrEXS" name="solemn.mp3" compile="0" resource="1" file="BinaryResources/sounds/solemn.mp3"
xcodeResource="1"/>
</GROUP>
</GROUP>
<GROUP id="{1AF8C966-743E-3F06-189C-AADD83767CB4}" name="Source">
@@ -104,7 +110,7 @@
<MODULEPATH id="juce_audio_processors" path="../../modules"/>
</MODULEPATHS>
</ANDROIDSTUDIO>
<XCODE_MAC targetFolder="Builds/MacOSX">
<XCODE_MAC targetFolder="Builds/MacOSX" iosPushNotifications="1" customXcodeResourceFolders="./BinaryResources/images">
<CONFIGURATIONS>
<CONFIGURATION name="Debug" isDebug="1" optimisation="1" targetName="PushNotificationsDemo"
enablePluginBinaryCopyStep="1"/>


+ 347
- 131
examples/PushNotificationsDemo/Source/MainComponent.cpp View File

@@ -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, &paramsOneView, false);
localNotificationsTabs.addTab (tabNames[1], colour, &paramsTwoView, false);
#if JUCE_ANDROID
localNotificationsTabs.addTab ("Opt. params2", colour, &optionalParamsTwoView, false);
localNotificationsTabs.addTab ("Opt. params3", colour, &optionalParamsThreeView, false);
localNotificationsTabs.addTab (tabNames[2], colour, &paramsThreeView, false);
localNotificationsTabs.addTab (tabNames[3], colour, &paramsFourView, 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 == &paramControls.accentColourButton)
setupAccentColour();
else if (b == &optionalParamsThreeView.ledColourButton)
else if (b == &paramControls.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 == &paramControls.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,82 +458,84 @@ 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;
a .title = a .identifier = "Input Text Here";
a2.title = a2.identifier = "No";
a .style = PushNotifications::Notification::Action::text;
a2.style = PushNotifications::Notification::Action::button;
a .icon = "ic_stat_name4";
a2.icon = "ic_stat_name5";
a.textInputPlaceholder = "placeholder text ...";
n.actions.add (a);
n.actions.add (a2);
}
else if (optionalParamsOneView.actionsComboBox.getSelectedItemIndex() == 3)
else if (paramControls.actionsComboBox.getSelectedItemIndex() == 3)
{
PushNotifications::Notification::Action a, a2;
a .title = "Input Text Here";
a2.title = "No";
a .style = PushNotifications::Notification::Action::text;
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";
a.textInputPlaceholder = "placeholder text ...";
n.actions.add (a);
n.actions.add (a2);
}
else if (optionalParamsOneView.actionsComboBox.getSelectedItemIndex() == 4)
else if (paramControls.actionsComboBox.getSelectedItemIndex() == 4)
{
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";
@@ -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<int> 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);
CallOutBox::launchAsynchronously (optionalParamsThreeView.accentColourSelector, optionalParamsThreeView.accentColourButton.getScreenBounds(), nullptr);
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 (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);
CallOutBox::launchAsynchronously (optionalParamsThreeView.ledColourSelector, optionalParamsThreeView.accentColourButton.getScreenBounds(), nullptr);
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 (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<PushNotifications::Channel> 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;
}


+ 127
- 402
examples/PushNotificationsDemo/Source/MainComponent.h View File

@@ -88,230 +88,61 @@ private:
void upstreamMessageSendingError (const String& messageId, const String& error) override;
static Array<PushNotifications::Channel> 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<int> 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);
fireInComboBox.addItem ("Now", 1);
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<int> 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 identifierLabel { "identifierLabel", "Identifier" };
TextEditor identifierEditor;
Label titleLabel { "titleLabel", "Title" };
TextEditor titleEditor;
Label bodyLabel { "bodyLabel", "Body" };
TextEditor bodyEditor;
Label categoryLabel { "categoryLabel", "Category" };
ComboBox categoryComboBox;
Label channelIdLabel { "channelIdLabel", "Channel ID" };
ComboBox channelIdComboBox;
Label iconLabel { "iconLabel", "Icon" };
ComboBox iconComboBox;
Label subtitleLabel { "subtitleLabel", "Subtitle" };
TextEditor subtitleEditor;
Label badgeNumberLabel { "badgeNumberLabel", "BadgeNumber" };
ComboBox badgeNumberComboBox;
Label soundToPlayLabel { "soundToPlayLabel", "SoundToPlay" };
ComboBox soundToPlayComboBox;
Label propertiesLabel { "propertiesLabel", "Properties" };
TextEditor propertiesEditor;
Label fireInLabel { "fireInLabel", "Fire in" };
ComboBox fireInComboBox;
Label repeatLabel { "repeatLabel", "Repeat" };
ToggleButton repeatButton;
#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<int> 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<int> 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<RowComponent> 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)


+ 9
- 7
extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Xcode.h View File

@@ -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", "<string>development</string>");
if (isPushNotificationsEnabled())
entitlements.set (isiOS() ? "aps-environment"
: "com.apple.developer.aps-environment",
"<string>development</string>");
}
if (isAppGroupsEnabled())


+ 126
- 0
modules/juce_core/native/juce_osx_ObjCHelpers.h View File

@@ -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<var> 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 <typename RectangleType>
static NSRect makeNSRect (const RectangleType& r) noexcept


+ 93
- 2
modules/juce_events/native/juce_mac_MessageManager.mm View File

@@ -102,7 +102,7 @@ private:
{
AppDelegateClass() : ObjCClass<NSObject> ("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<NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>*> ("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<NSApplicationDelegate, NSUserNotificationCenterDelegate>* delegate)
{
object_setInstanceVariable (self, "pushNotificationsDelegate", delegate);
}
static NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>* getPushNotificationsDelegate (id self)
{
return getIvar<NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>*> (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
};
};


+ 6
- 0
modules/juce_gui_extra/juce_gui_extra.cpp View File

@@ -55,6 +55,12 @@
#import <IOKit/hid/IOHIDKeys.h>
#import <IOKit/pwr_mgt/IOPMLib.h>
#if JUCE_PUSH_NOTIFICATIONS
#import <Foundation/NSUserNotification.h>
#include "native/juce_mac_PushNotifications.cpp"
#endif
//==============================================================================
#elif JUCE_IOS
#if JUCE_PUSH_NOTIFICATIONS


+ 3
- 3
modules/juce_gui_extra/misc/juce_PushNotifications.cpp View File

@@ -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, {});


+ 21
- 5
modules/juce_gui_extra/misc/juce_PushNotifications.h View File

@@ -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;


+ 5
- 131
modules/juce_gui_extra/native/juce_ios_PushNotifications.cpp View File

@@ -34,84 +34,6 @@ template <> struct ContainerDeletePolicy<NSObject<UIApplicationDelegate, UNUserN
namespace PushNotificationsDelegateDetails
{
//==============================================================================
NSArray* varArrayToNSArray (const var& varToParse);
NSDictionary* varObjectToNSDictionary (const var& varToParse)
{
NSMutableDictionary* dictionary = [NSMutableDictionary dictionary];
if (varToParse.isObject())
{
auto* dynamicObject = varToParse.getDynamicObject();
auto& properties = dynamicObject->getProperties();
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<var> 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);
}


+ 557
- 0
modules/juce_gui_extra/native/juce_mac_PushNotifications.cpp View File

@@ -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<NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>> { 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<Action> 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<NSApplicationDelegate> 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<NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>> delegate;
private:
struct Class : public ObjCClass<NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>>
{
Class() : ObjCClass<NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>> ("JucePushNotificationsDelegate_")
{
addIvar<PushNotificationsDelegate*> ("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<PushNotificationsDelegate*> (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<PushNotifications::Notification> 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<ChannelGroup>& groups, const Array<Channel>& channels)
{
ignoreUnused (groups, channels);
}
void getPendingLocalNotifications() const
{
Array<PushNotifications::Notification> 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

Loading…
Cancel
Save