| @@ -31,19 +31,25 @@ | |||||
| </GROUP> | </GROUP> | ||||
| <GROUP id="{D7B3F6B4-2E9A-E281-831A-2B67D6D4C4A0}" name="sounds"> | <GROUP id="{D7B3F6B4-2E9A-E281-831A-2B67D6D4C4A0}" name="sounds"> | ||||
| <FILE id="il8LpK" name="demonstrative.caf" compile="0" resource="1" | <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 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 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 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> | </GROUP> | ||||
| <GROUP id="{1AF8C966-743E-3F06-189C-AADD83767CB4}" name="Source"> | <GROUP id="{1AF8C966-743E-3F06-189C-AADD83767CB4}" name="Source"> | ||||
| @@ -104,7 +110,7 @@ | |||||
| <MODULEPATH id="juce_audio_processors" path="../../modules"/> | <MODULEPATH id="juce_audio_processors" path="../../modules"/> | ||||
| </MODULEPATHS> | </MODULEPATHS> | ||||
| </ANDROIDSTUDIO> | </ANDROIDSTUDIO> | ||||
| <XCODE_MAC targetFolder="Builds/MacOSX"> | |||||
| <XCODE_MAC targetFolder="Builds/MacOSX" iosPushNotifications="1" customXcodeResourceFolders="./BinaryResources/images"> | |||||
| <CONFIGURATIONS> | <CONFIGURATIONS> | ||||
| <CONFIGURATION name="Debug" isDebug="1" optimisation="1" targetName="PushNotificationsDemo" | <CONFIGURATION name="Debug" isDebug="1" optimisation="1" targetName="PushNotificationsDemo" | ||||
| enablePluginBinaryCopyStep="1"/> | enablePluginBinaryCopyStep="1"/> | ||||
| @@ -29,7 +29,10 @@ | |||||
| //============================================================================== | //============================================================================== | ||||
| MainContentComponent::MainContentComponent() | MainContentComponent::MainContentComponent() | ||||
| { | { | ||||
| #if JUCE_ANDROID || JUCE_IOS | |||||
| setupControls(); | |||||
| distributeControls(); | |||||
| #if JUCE_PUSH_NOTIFICATIONS | |||||
| addAndMakeVisible (headerLabel); | addAndMakeVisible (headerLabel); | ||||
| addAndMakeVisible (mainTabs); | addAndMakeVisible (mainTabs); | ||||
| addAndMakeVisible (sendButton); | addAndMakeVisible (sendButton); | ||||
| @@ -40,12 +43,18 @@ MainContentComponent::MainContentComponent() | |||||
| headerLabel.setJustificationType (Justification::centred); | headerLabel.setJustificationType (Justification::centred); | ||||
| notAvailableYetLabel.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); | const auto colour = getLookAndFeel().findColour (ResizableWindow::backgroundColourId); | ||||
| localNotificationsTabs.addTab ("Req. params", colour, &requiredParamsView, false); | |||||
| localNotificationsTabs.addTab ("Opt. params1", colour, &optionalParamsOneView, false); | |||||
| localNotificationsTabs.addTab (tabNames[0], colour, ¶msOneView, false); | |||||
| localNotificationsTabs.addTab (tabNames[1], colour, ¶msTwoView, false); | |||||
| #if JUCE_ANDROID | #if JUCE_ANDROID | ||||
| localNotificationsTabs.addTab ("Opt. params2", colour, &optionalParamsTwoView, false); | |||||
| localNotificationsTabs.addTab ("Opt. params3", colour, &optionalParamsThreeView, false); | |||||
| localNotificationsTabs.addTab (tabNames[2], colour, ¶msThreeView, false); | |||||
| localNotificationsTabs.addTab (tabNames[3], colour, ¶msFourView, false); | |||||
| #endif | #endif | ||||
| localNotificationsTabs.addTab ("Aux. actions", colour, &auxActionsView, false); | localNotificationsTabs.addTab ("Aux. actions", colour, &auxActionsView, false); | ||||
| @@ -63,7 +72,7 @@ MainContentComponent::MainContentComponent() | |||||
| auxActionsView.getDeliveredNotificationsButton .addListener (this); | auxActionsView.getDeliveredNotificationsButton .addListener (this); | ||||
| auxActionsView.removeDeliveredNotifWithIdButton.addListener (this); | auxActionsView.removeDeliveredNotifWithIdButton.addListener (this); | ||||
| auxActionsView.removeAllDeliveredNotifsButton .addListener (this); | auxActionsView.removeAllDeliveredNotifsButton .addListener (this); | ||||
| #if JUCE_IOS | |||||
| #if JUCE_IOS || JUCE_MAC | |||||
| auxActionsView.getPendingNotificationsButton .addListener (this); | auxActionsView.getPendingNotificationsButton .addListener (this); | ||||
| auxActionsView.removePendingNotifWithIdButton.addListener (this); | auxActionsView.removePendingNotifWithIdButton.addListener (this); | ||||
| auxActionsView.removeAllPendingNotifsButton .addListener (this); | auxActionsView.removeAllPendingNotifsButton .addListener (this); | ||||
| @@ -74,16 +83,16 @@ MainContentComponent::MainContentComponent() | |||||
| remoteView.subscribeToSportsButton .addListener (this); | remoteView.subscribeToSportsButton .addListener (this); | ||||
| remoteView.unsubscribeFromSportsButton.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()); | jassert (PushNotifications::getInstance()->areNotificationsEnabled()); | ||||
| PushNotifications::getInstance()->addListener (this); | 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 | #elif JUCE_ANDROID | ||||
| PushNotifications::ChannelGroup cg { "demoGroup", "demo group" }; | PushNotifications::ChannelGroup cg { "demoGroup", "demo group" }; | ||||
| PushNotifications::getInstance()->setupChannels ({{ cg }}, getAndroidChannels()); | PushNotifications::getInstance()->setupChannels ({{ cg }}, getAndroidChannels()); | ||||
| @@ -95,6 +104,200 @@ MainContentComponent::~MainContentComponent() | |||||
| PushNotifications::getInstance()->removeListener (this); | 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) | void MainContentComponent::paint (Graphics& g) | ||||
| { | { | ||||
| g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId)); | g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId)); | ||||
| @@ -117,9 +320,9 @@ void MainContentComponent::buttonClicked (Button* b) | |||||
| { | { | ||||
| if (b == &sendButton) | if (b == &sendButton) | ||||
| sendLocalNotification(); | sendLocalNotification(); | ||||
| else if (b == &optionalParamsThreeView.accentColourButton) | |||||
| else if (b == ¶mControls.accentColourButton) | |||||
| setupAccentColour(); | setupAccentColour(); | ||||
| else if (b == &optionalParamsThreeView.ledColourButton) | |||||
| else if (b == ¶mControls.ledColourButton) | |||||
| setupLedColour(); | setupLedColour(); | ||||
| else if (b == &auxActionsView.getDeliveredNotificationsButton) | else if (b == &auxActionsView.getDeliveredNotificationsButton) | ||||
| getDeliveredNotifications(); | getDeliveredNotifications(); | ||||
| @@ -127,7 +330,7 @@ void MainContentComponent::buttonClicked (Button* b) | |||||
| PushNotifications::getInstance()->removeDeliveredNotification (auxActionsView.deliveredNotifIdentifier.getText()); | PushNotifications::getInstance()->removeDeliveredNotification (auxActionsView.deliveredNotifIdentifier.getText()); | ||||
| else if (b == &auxActionsView.removeAllDeliveredNotifsButton) | else if (b == &auxActionsView.removeAllDeliveredNotifsButton) | ||||
| PushNotifications::getInstance()->removeAllDeliveredNotifications(); | PushNotifications::getInstance()->removeAllDeliveredNotifications(); | ||||
| #if JUCE_IOS | |||||
| #if JUCE_IOS || JUCE_MAC | |||||
| else if (b == &auxActionsView.getPendingNotificationsButton) | else if (b == &auxActionsView.getPendingNotificationsButton) | ||||
| PushNotifications::getInstance()->getPendingLocalNotifications(); | PushNotifications::getInstance()->getPendingLocalNotifications(); | ||||
| else if (b == &auxActionsView.removePendingNotifWithIdButton) | else if (b == &auxActionsView.removePendingNotifWithIdButton) | ||||
| @@ -141,7 +344,10 @@ void MainContentComponent::buttonClicked (Button* b) | |||||
| DBG ("token = " + token); | 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 | #if JUCE_ANDROID | ||||
| else if (b == &remoteView.sendRemoteMessageButton) | else if (b == &remoteView.sendRemoteMessageButton) | ||||
| @@ -172,19 +378,15 @@ void MainContentComponent::buttonClicked (Button* b) | |||||
| void MainContentComponent::comboBoxChanged (ComboBox* comboBoxThatHasChanged) | void MainContentComponent::comboBoxChanged (ComboBox* comboBoxThatHasChanged) | ||||
| { | { | ||||
| #if JUCE_IOS | |||||
| if (comboBoxThatHasChanged == &optionalParamsOneView.fireInComboBox) | |||||
| if (comboBoxThatHasChanged == ¶mControls.fireInComboBox) | |||||
| { | { | ||||
| const bool repeatsAllowed = optionalParamsOneView.fireInComboBox.getSelectedItemIndex() >= 6; | |||||
| const bool repeatsAllowed = paramControls.fireInComboBox.getSelectedItemIndex() >= 6; | |||||
| optionalParamsOneView.repeatButton.setEnabled (repeatsAllowed); | |||||
| paramControls.repeatButton.setEnabled (repeatsAllowed); | |||||
| if (! repeatsAllowed) | if (! repeatsAllowed) | ||||
| optionalParamsOneView.repeatButton.setToggleState (false, NotificationType::sendNotification); | |||||
| paramControls.repeatButton.setToggleState (false, NotificationType::sendNotification); | |||||
| } | } | ||||
| #else | |||||
| ignoreUnused (comboBoxThatHasChanged); | |||||
| #endif | |||||
| } | } | ||||
| void MainContentComponent::sendLocalNotification() | void MainContentComponent::sendLocalNotification() | ||||
| @@ -222,23 +424,32 @@ void MainContentComponent::sendLocalNotification() | |||||
| void MainContentComponent::fillRequiredParams (PushNotifications::Notification& n) | 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 | #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! | // 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 = new PushNotifications::Notification(); | ||||
| n.publicVersion->identifier = "blahblahblah"; | n.publicVersion->identifier = "blahblahblah"; | ||||
| @@ -247,82 +458,84 @@ void MainContentComponent::fillRequiredParams (PushNotifications::Notification& | |||||
| n.publicVersion->icon = n.icon; | n.publicVersion->icon = n.icon; | ||||
| #if __ANDROID_API__ >= 26 | #if __ANDROID_API__ >= 26 | ||||
| n.channelId = String (requiredParamsView.channelIdComboBox.getSelectedItemIndex() + 1); | |||||
| n.channelId = String (paramControls.channelIdComboBox.getSelectedItemIndex() + 1); | |||||
| #endif | #endif | ||||
| #endif | #endif | ||||
| } | } | ||||
| void MainContentComponent::fillOptionalParamsOne (PushNotifications::Notification& n) | 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 | #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); | 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); | 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); | 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); | 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.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; | PushNotifications::Notification::Action a, a2; | ||||
| a .style = PushNotifications::Notification::Action::button; | a .style = PushNotifications::Notification::Action::button; | ||||
| a2.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 (a); | ||||
| n.actions.add (a2); | n.actions.add (a2); | ||||
| } | } | ||||
| else if (optionalParamsOneView.actionsComboBox.getSelectedItemIndex() == 2) | |||||
| else if (paramControls.actionsComboBox.getSelectedItemIndex() == 2) | |||||
| { | { | ||||
| PushNotifications::Notification::Action a, a2; | 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; | a2.style = PushNotifications::Notification::Action::button; | ||||
| a .icon = "ic_stat_name4"; | a .icon = "ic_stat_name4"; | ||||
| a2.icon = "ic_stat_name5"; | a2.icon = "ic_stat_name5"; | ||||
| a.textInputPlaceholder = "placeholder text ..."; | |||||
| n.actions.add (a); | n.actions.add (a); | ||||
| n.actions.add (a2); | n.actions.add (a2); | ||||
| } | } | ||||
| else if (optionalParamsOneView.actionsComboBox.getSelectedItemIndex() == 3) | |||||
| else if (paramControls.actionsComboBox.getSelectedItemIndex() == 3) | |||||
| { | { | ||||
| PushNotifications::Notification::Action a, a2; | 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; | a2.style = PushNotifications::Notification::Action::button; | ||||
| a .icon = "ic_stat_name4"; | a .icon = "ic_stat_name4"; | ||||
| a2.icon = "ic_stat_name5"; | a2.icon = "ic_stat_name5"; | ||||
| a.textInputPlaceholder = "placeholder text ..."; | |||||
| n.actions.add (a); | n.actions.add (a); | ||||
| n.actions.add (a2); | n.actions.add (a2); | ||||
| } | } | ||||
| else if (optionalParamsOneView.actionsComboBox.getSelectedItemIndex() == 4) | |||||
| else if (paramControls.actionsComboBox.getSelectedItemIndex() == 4) | |||||
| { | { | ||||
| PushNotifications::Notification::Action a, a2; | 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; | a .style = PushNotifications::Notification::Action::text; | ||||
| a2.style = PushNotifications::Notification::Action::button; | a2.style = PushNotifications::Notification::Action::button; | ||||
| a .icon = "ic_stat_name4"; | a .icon = "ic_stat_name4"; | ||||
| @@ -342,102 +555,102 @@ void MainContentComponent::fillOptionalParamsTwo (PushNotifications::Notificatio | |||||
| using Notification = PushNotifications::Notification; | using Notification = PushNotifications::Notification; | ||||
| Notification::Progress progress; | 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.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) | 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; | using Notification = PushNotifications::Notification; | ||||
| Notification::LedBlinkPattern ledBlinkPattern; | 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; | n.ledBlinkPattern = ledBlinkPattern; | ||||
| Array<int> vibrationPattern; | 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.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; | n.timeoutAfterMs = index * 1000 + 4000; | ||||
| } | } | ||||
| } | } | ||||
| void MainContentComponent::setupAccentColour() | 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() | 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) | 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) | 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) | void MainContentComponent::handleNotification (bool isLocalNotification, const PushNotifications::Notification& n) | ||||
| @@ -576,9 +789,15 @@ Array<PushNotifications::Channel> MainContentComponent::getAndroidChannels() | |||||
| return { ch1, ch2, ch3 }; | 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 Action = PushNotifications::Settings::Action; | ||||
| using Category = PushNotifications::Settings::Category; | using Category = PushNotifications::Settings::Category; | ||||
| @@ -617,11 +836,8 @@ PushNotifications::Settings MainContentComponent::getIosSettings() | |||||
| textCategory.actions = { textAction }; | textCategory.actions = { textAction }; | ||||
| textCategory.sendDismissAction = true; | textCategory.sendDismissAction = true; | ||||
| PushNotifications::Settings settings; | |||||
| settings.allowAlert = true; | |||||
| settings.allowBadge = true; | |||||
| settings.allowSound = true; | |||||
| settings.categories = { okCategory, okCancelCategory, textCategory }; | settings.categories = { okCategory, okCancelCategory, textCategory }; | ||||
| #endif | |||||
| return settings; | return settings; | ||||
| } | } | ||||
| @@ -88,230 +88,61 @@ private: | |||||
| void upstreamMessageSendingError (const String& messageId, const String& error) override; | void upstreamMessageSendingError (const String& messageId, const String& error) override; | ||||
| static Array<PushNotifications::Channel> getAndroidChannels(); | static Array<PushNotifications::Channel> getAndroidChannels(); | ||||
| #elif JUCE_IOS | |||||
| static PushNotifications::Settings getIosSettings(); | |||||
| #elif JUCE_IOS || JUCE_MAC | |||||
| static PushNotifications::Settings getNotificationSettings(); | |||||
| #endif | #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 | 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(); | 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" }; | Label fireInLabel { "fireInLabel", "Fire in" }; | ||||
| ComboBox fireInComboBox; | ComboBox fireInComboBox; | ||||
| Label repeatLabel { "repeatLabel", "Repeat" }; | Label repeatLabel { "repeatLabel", "Repeat" }; | ||||
| ToggleButton repeatButton; | ToggleButton repeatButton; | ||||
| #elif JUCE_ANDROID | |||||
| Label largeIconLabel { "largeIconLabel", "Large Icon" }; | Label largeIconLabel { "largeIconLabel", "Large Icon" }; | ||||
| ComboBox largeIconComboBox; | ComboBox largeIconComboBox; | ||||
| Label badgeIconLabel { "badgeIconLabel", "Badge Icon" }; | Label badgeIconLabel { "badgeIconLabel", "Badge Icon" }; | ||||
| @@ -324,106 +155,6 @@ private: | |||||
| ToggleButton alertOnlyOnceButton; | ToggleButton alertOnlyOnceButton; | ||||
| Label actionsLabel { "actionsLabel", "Actions" }; | Label actionsLabel { "actionsLabel", "Actions" }; | ||||
| ComboBox actionsComboBox; | 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" }; | Label progressMaxLabel { "progressMaxLabel", "ProgressMax" }; | ||||
| ComboBox progressMaxComboBox; | ComboBox progressMaxComboBox; | ||||
| @@ -431,8 +162,8 @@ private: | |||||
| ComboBox progressCurrentComboBox; | ComboBox progressCurrentComboBox; | ||||
| Label progressIndeterminateLabel { "progressIndeterminateLabel", "ProgressIndeterminate" }; | Label progressIndeterminateLabel { "progressIndeterminateLabel", "ProgressIndeterminate" }; | ||||
| ToggleButton progressIndeterminateButton; | ToggleButton progressIndeterminateButton; | ||||
| Label categoryLabel { "categoryLabel", "Category" }; | |||||
| ComboBox categoryComboBox; | |||||
| Label notifCategoryLabel { "notifCategoryLabel", "Category" }; | |||||
| ComboBox notifCategoryComboBox; | |||||
| Label priorityLabel { "priorityLabel", "Priority" }; | Label priorityLabel { "priorityLabel", "Priority" }; | ||||
| ComboBox priorityComboBox; | ComboBox priorityComboBox; | ||||
| Label personLabel { "personLabel", "Person" }; | Label personLabel { "personLabel", "Person" }; | ||||
| @@ -447,84 +178,6 @@ private: | |||||
| ToggleButton groupSummaryButton; | ToggleButton groupSummaryButton; | ||||
| Label groupAlertBehaviourLabel { "groupAlertBehaviourLabel", "GroupAlertBehaviour" }; | Label groupAlertBehaviourLabel { "groupAlertBehaviourLabel", "GroupAlertBehaviour" }; | ||||
| ComboBox groupAlertBehaviourComboBox; | 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" }; | Label accentColourLabel { "accentColourLabel", "AccentColour" }; | ||||
| TextButton accentColourButton; | TextButton accentColourButton; | ||||
| @@ -551,6 +204,45 @@ private: | |||||
| ColourSelector* ledColourSelector = nullptr; | 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 | struct AuxActionsView : public Component | ||||
| { | { | ||||
| AuxActionsView() | AuxActionsView() | ||||
| @@ -559,7 +251,7 @@ private: | |||||
| addAndMakeVisible (removeDeliveredNotifWithIdButton); | addAndMakeVisible (removeDeliveredNotifWithIdButton); | ||||
| addAndMakeVisible (deliveredNotifIdentifier); | addAndMakeVisible (deliveredNotifIdentifier); | ||||
| addAndMakeVisible (removeAllDeliveredNotifsButton); | addAndMakeVisible (removeAllDeliveredNotifsButton); | ||||
| #if JUCE_IOS | |||||
| #if JUCE_IOS || JUCE_MAC | |||||
| addAndMakeVisible (getPendingNotificationsButton); | addAndMakeVisible (getPendingNotificationsButton); | ||||
| addAndMakeVisible (removePendingNotifWithIdButton); | addAndMakeVisible (removePendingNotifWithIdButton); | ||||
| addAndMakeVisible (pendingNotifIdentifier); | addAndMakeVisible (pendingNotifIdentifier); | ||||
| @@ -586,7 +278,7 @@ private: | |||||
| removeAllDeliveredNotifsButton .setBounds (bounds.removeFromTop (rowHeight)); | removeAllDeliveredNotifsButton .setBounds (bounds.removeFromTop (rowHeight)); | ||||
| #if JUCE_IOS | |||||
| #if JUCE_IOS || JUCE_MAC | |||||
| getPendingNotificationsButton .setBounds (bounds.removeFromTop (rowHeight)); | getPendingNotificationsButton .setBounds (bounds.removeFromTop (rowHeight)); | ||||
| rowBounds = bounds.removeFromTop (rowHeight); | rowBounds = bounds.removeFromTop (rowHeight); | ||||
| @@ -601,12 +293,10 @@ private: | |||||
| TextButton removeDeliveredNotifWithIdButton { "Remove Delivered Notif With ID:" }; | TextButton removeDeliveredNotifWithIdButton { "Remove Delivered Notif With ID:" }; | ||||
| TextEditor deliveredNotifIdentifier; | TextEditor deliveredNotifIdentifier; | ||||
| TextButton removeAllDeliveredNotifsButton { "Remove All Delivered Notifs" }; | TextButton removeAllDeliveredNotifsButton { "Remove All Delivered Notifs" }; | ||||
| #if JUCE_IOS | |||||
| TextButton getPendingNotificationsButton { "Get Pending Notifications" }; | TextButton getPendingNotificationsButton { "Get Pending Notifications" }; | ||||
| TextButton removePendingNotifWithIdButton { "Remove Pending Notif With ID:" }; | TextButton removePendingNotifWithIdButton { "Remove Pending Notif With ID:" }; | ||||
| TextEditor pendingNotifIdentifier; | TextEditor pendingNotifIdentifier; | ||||
| TextButton removeAllPendingNotifsButton { "Remove All Pending Notifs" }; | TextButton removeAllPendingNotifsButton { "Remove All Pending Notifs" }; | ||||
| #endif | |||||
| }; | }; | ||||
| struct RemoteView : public Component | struct RemoteView : public Component | ||||
| @@ -641,17 +331,52 @@ private: | |||||
| TextButton unsubscribeFromSportsButton { "UnsubscribeFromSports" }; | 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) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent) | ||||
| @@ -249,9 +249,6 @@ public: | |||||
| props.add (new BooleanPropertyComponent (getBackgroundBleValue(), "Bluetooth MIDI background capability", "Enabled"), | 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."); | "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"), | 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."); | "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."); | "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), | 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 " | "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 " | "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 | && type == Target::StandalonePlugIn | ||||
| && owner.getProject().shouldEnableIAA()) ? 1 : 0; | && 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); | auto sandboxEnabled = (type == Target::AudioUnitv3PlugIn ? 1 : 0); | ||||
| attributes << "SystemCapabilities = {"; | attributes << "SystemCapabilities = {"; | ||||
| @@ -1146,7 +1146,7 @@ public: | |||||
| if (owner.isInAppPurchasesEnabled()) | if (owner.isInAppPurchasesEnabled()) | ||||
| defines.set ("JUCE_IN_APP_PURCHASES", "1"); | defines.set ("JUCE_IN_APP_PURCHASES", "1"); | ||||
| if (owner.iOS && owner.isPushNotificationsEnabled()) | |||||
| if (owner.isPushNotificationsEnabled()) | |||||
| defines.set ("JUCE_PUSH_NOTIFICATIONS", "1"); | defines.set ("JUCE_PUSH_NOTIFICATIONS", "1"); | ||||
| defines = mergePreprocessorDefs (defines, owner.getAllPreprocessorDefs (config, type)); | defines = mergePreprocessorDefs (defines, owner.getAllPreprocessorDefs (config, type)); | ||||
| @@ -2552,8 +2552,10 @@ private: | |||||
| } | } | ||||
| else | 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()) | if (isAppGroupsEnabled()) | ||||
| @@ -67,6 +67,132 @@ static inline NSArray* createNSArrayFromStringArray (const StringArray& strings) | |||||
| return [array autorelease]; | 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 | #if JUCE_MAC | ||||
| template <typename RectangleType> | template <typename RectangleType> | ||||
| static NSRect makeNSRect (const RectangleType& r) noexcept | static NSRect makeNSRect (const RectangleType& r) noexcept | ||||
| @@ -102,7 +102,7 @@ private: | |||||
| { | { | ||||
| AppDelegateClass() : ObjCClass<NSObject> ("JUCEAppDelegate_") | AppDelegateClass() : ObjCClass<NSObject> ("JUCEAppDelegate_") | ||||
| { | { | ||||
| addMethod (@selector (applicationWillFinishLaunching:), applicationWillFinishLaunching, "v@:@@"); | |||||
| addMethod (@selector (applicationWillFinishLaunching:), applicationWillFinishLaunching, "v@:@"); | |||||
| addMethod (@selector (getUrl:withReplyEvent:), getUrl_withReplyEvent, "v@:@@"); | addMethod (@selector (getUrl:withReplyEvent:), getUrl_withReplyEvent, "v@:@@"); | ||||
| addMethod (@selector (applicationShouldTerminate:), applicationShouldTerminate, "I@:@"); | addMethod (@selector (applicationShouldTerminate:), applicationShouldTerminate, "I@:@"); | ||||
| addMethod (@selector (applicationWillTerminate:), applicationWillTerminate, "v@:@"); | addMethod (@selector (applicationWillTerminate:), applicationWillTerminate, "v@:@"); | ||||
| @@ -116,11 +116,22 @@ private: | |||||
| addMethod (@selector (mainMenuTrackingEnded:), mainMenuTrackingEnded, "v@:@"); | addMethod (@selector (mainMenuTrackingEnded:), mainMenuTrackingEnded, "v@:@"); | ||||
| addMethod (@selector (dummyMethod), dummyMethod, "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(); | registerClass(); | ||||
| } | } | ||||
| private: | private: | ||||
| static void applicationWillFinishLaunching (id self, SEL, NSApplication*, NSNotification*) | |||||
| static void applicationWillFinishLaunching (id self, SEL, NSNotification*) | |||||
| { | { | ||||
| [[NSAppleEventManager sharedAppleEventManager] setEventHandler: self | [[NSAppleEventManager sharedAppleEventManager] setEventHandler: self | ||||
| andSelector: @selector (getUrl:withReplyEvent:) | andSelector: @selector (getUrl:withReplyEvent:) | ||||
| @@ -128,6 +139,19 @@ private: | |||||
| andEventID: kAEGetURL]; | 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*) | static NSApplicationTerminateReply applicationShouldTerminate (id /*self*/, SEL, NSApplication*) | ||||
| { | { | ||||
| if (auto* app = JUCEApplicationBase::getInstance()) | if (auto* app = JUCEApplicationBase::getInstance()) | ||||
| @@ -216,6 +240,73 @@ private: | |||||
| return s; | 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 | |||||
| }; | }; | ||||
| }; | }; | ||||
| @@ -55,6 +55,12 @@ | |||||
| #import <IOKit/hid/IOHIDKeys.h> | #import <IOKit/hid/IOHIDKeys.h> | ||||
| #import <IOKit/pwr_mgt/IOPMLib.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 | #elif JUCE_IOS | ||||
| #if JUCE_PUSH_NOTIFICATIONS | #if JUCE_PUSH_NOTIFICATIONS | ||||
| @@ -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; } | bool PushNotifications::Notification::isValid() const noexcept { return true; } | ||||
| #endif | #endif | ||||
| @@ -49,7 +49,7 @@ void PushNotifications::removeListener (Listener* l) { listeners.remove (l); } | |||||
| void PushNotifications::requestPermissionsWithSettings (const PushNotifications::Settings& settings) | void PushNotifications::requestPermissionsWithSettings (const PushNotifications::Settings& settings) | ||||
| { | { | ||||
| #if JUCE_PUSH_NOTIFICATIONS && JUCE_IOS | |||||
| #if JUCE_PUSH_NOTIFICATIONS && (JUCE_IOS || JUCE_MAC) | |||||
| pimpl->requestPermissionsWithSettings (settings); | pimpl->requestPermissionsWithSettings (settings); | ||||
| #else | #else | ||||
| ignoreUnused (settings); | ignoreUnused (settings); | ||||
| @@ -59,7 +59,7 @@ void PushNotifications::requestPermissionsWithSettings (const PushNotifications: | |||||
| void PushNotifications::requestSettingsUsed() | void PushNotifications::requestSettingsUsed() | ||||
| { | { | ||||
| #if JUCE_PUSH_NOTIFICATIONS && JUCE_IOS | |||||
| #if JUCE_PUSH_NOTIFICATIONS && (JUCE_IOS || JUCE_MAC) | |||||
| pimpl->requestSettingsUsed(); | pimpl->requestSettingsUsed(); | ||||
| #else | #else | ||||
| listeners.call (&PushNotifications::Listener::notificationSettingsReceived, {}); | listeners.call (&PushNotifications::Listener::notificationSettingsReceived, {}); | ||||
| @@ -124,6 +124,14 @@ public: | |||||
| URL soundToPlay; /**< Optional: empty when the notification should be silent. When the name is set to | 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. | "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 | 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 extension. For instance, if your bundle contains "sounds" folder with "my_sound.caf" | ||||
| file, then the URL should be "sounds/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 | /** 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, | To setup push notifications for current device, provide permissions required, | ||||
| as well as register categories of notifications you want to support. Each | 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 | 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). | 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(); | void requestSettingsUsed(); | ||||
| @@ -486,7 +502,7 @@ public: | |||||
| //========================================================================== | //========================================================================== | ||||
| /** Checks whether notifications are enabled for given application. | /** 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; | bool areNotificationsEnabled() const; | ||||
| @@ -34,84 +34,6 @@ template <> struct ContainerDeletePolicy<NSObject<UIApplicationDelegate, UNUserN | |||||
| namespace PushNotificationsDelegateDetails | 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 Action = PushNotifications::Settings::Action; | ||||
| using Category = PushNotifications::Settings::Category; | using Category = PushNotifications::Settings::Category; | ||||
| @@ -210,7 +132,7 @@ namespace PushNotificationsDelegateDetails | |||||
| auto triggerTime = Time::getCurrentTime() + RelativeTime (n.triggerIntervalSec); | auto triggerTime = Time::getCurrentTime() + RelativeTime (n.triggerIntervalSec); | ||||
| notification.fireDate = [NSDate dateWithTimeIntervalSince1970: triggerTime.toMilliseconds() / 1000.]; | notification.fireDate = [NSDate dateWithTimeIntervalSince1970: triggerTime.toMilliseconds() / 1000.]; | ||||
| notification.userInfo = PushNotificationsDelegateDetails::varObjectToNSDictionary (n.properties); | |||||
| notification.userInfo = varObjectToNSDictionary (n.properties); | |||||
| auto soundToPlayString = n.soundToPlay.toString (true); | auto soundToPlayString = n.soundToPlay.toString (true); | ||||
| @@ -242,7 +164,7 @@ namespace PushNotificationsDelegateDetails | |||||
| else if (soundToPlayString.isNotEmpty()) | else if (soundToPlayString.isNotEmpty()) | ||||
| content.sound = [UNNotificationSound soundNamed: juceStringToNS (soundToPlayString)]; | 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")]; | [propsDict setObject: juceStringToNS (soundToPlayString) forKey: nsStringLiteral ("com.juce.soundName")]; | ||||
| content.userInfo = propsDict; | content.userInfo = propsDict; | ||||
| @@ -268,54 +190,6 @@ namespace PushNotificationsDelegateDetails | |||||
| } | } | ||||
| #endif | #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) | String getUserResponseFromNSDictionary (NSDictionary* dictionary) | ||||
| { | { | ||||
| if (dictionary == nil || dictionary.count == 0) | if (dictionary == nil || dictionary.count == 0) | ||||
| @@ -397,7 +271,7 @@ namespace PushNotificationsDelegateDetails | |||||
| n.category = nsStringToJuce (r.content.categoryIdentifier); | n.category = nsStringToJuce (r.content.categoryIdentifier); | ||||
| n.badgeNumber = r.content.badge.intValue; | n.badgeNumber = r.content.badge.intValue; | ||||
| auto userInfoVar = PushNotificationsDelegateDetails::nsDictionaryToVar (r.content.userInfo); | |||||
| auto userInfoVar = nsDictionaryToVar (r.content.userInfo); | |||||
| if (auto* object = userInfoVar.getDynamicObject()) | if (auto* object = userInfoVar.getDynamicObject()) | ||||
| { | { | ||||
| @@ -755,8 +629,6 @@ struct PushNotifications::Pimpl : private PushNotificationsDelegate | |||||
| #endif | #endif | ||||
| [[UIApplication sharedApplication] registerForRemoteNotifications]; | [[UIApplication sharedApplication] registerForRemoteNotifications]; | ||||
| initialised = true; | |||||
| } | } | ||||
| void requestSettingsUsed() | void requestSettingsUsed() | ||||
| @@ -980,6 +852,8 @@ struct PushNotifications::Pimpl : private PushNotificationsDelegate | |||||
| deviceToken = nsStringToJuce (deviceTokenString); | deviceToken = nsStringToJuce (deviceTokenString); | ||||
| initialised = true; | |||||
| owner.listeners.call (&PushNotifications::Listener::deviceTokenRefreshed, deviceToken); | owner.listeners.call (&PushNotifications::Listener::deviceTokenRefreshed, deviceToken); | ||||
| } | } | ||||
| @@ -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 | |||||