/*
  ==============================================================================

   This file is part of the JUCE framework examples.
   Copyright (c) Raw Material Software Limited

   The code included in this file is provided under the terms of the ISC license
   http://www.isc.org/downloads/software-support-policy/isc-license. Permission
   to use, copy, modify, and/or distribute this software for any purpose with or
   without fee is hereby granted provided that the above copyright notice and
   this permission notice appear in all copies.

   THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
   REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
   AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
   INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
   LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
   OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
   PERFORMANCE OF THIS SOFTWARE.

  ==============================================================================
*/

/*******************************************************************************
 The block below describes the properties of this PIP. A PIP is a short snippet
 of code that can be read by the Projucer and used to generate a JUCE project.

 BEGIN_JUCE_PIP_METADATA

 name:             PushNotificationsDemo
 version:          2.0.0
 vendor:           JUCE
 website:          http://juce.com
 description:      Showcases push notifications features. To run this demo you must enable the
                   "Push Notifications Capability" option in the Projucer exporter.

 dependencies:     juce_audio_basics, juce_audio_devices, juce_audio_formats,
                   juce_audio_processors, juce_audio_utils, juce_core,
                   juce_data_structures, juce_events, juce_graphics,
                   juce_gui_basics, juce_gui_extra, juce_audio_processors_headless
 exporters:        xcode_mac, xcode_iphone, androidstudio

 moduleFlags:      JUCE_STRICT_REFCOUNTEDPOINTER=1
                   JUCE_PUSH_NOTIFICATIONS=1

 type:             Component
 mainClass:        PushNotificationsDemo

 useLocalCopy:     1

 END_JUCE_PIP_METADATA

*******************************************************************************/

#pragma once

#include "../Assets/DemoUtilities.h"

/*
    To finish the setup of this demo, do the following:

1. Download google_services.json from your Firebase project.
2. Update "Remote Notifications Config File" path in Android exporter (this can be different for debug and release)
   to point to that json file.
3. Add image and sound resources by adding the following to "Extra Android Raw Resources" in Projucer:

../../Assets/Notifications/images/ic_stat_name.png
../../Assets/Notifications/images/ic_stat_name2.png
../../Assets/Notifications/images/ic_stat_name3.png
../../Assets/Notifications/images/ic_stat_name4.png
../../Assets/Notifications/images/ic_stat_name5.png
../../Assets/Notifications/images/ic_stat_name6.png
../../Assets/Notifications/images/ic_stat_name7.png
../../Assets/Notifications/images/ic_stat_name8.png
../../Assets/Notifications/images/ic_stat_name9.png
../../Assets/Notifications/images/ic_stat_name10.png
../../Assets/Notifications/sounds/demonstrative.mp3
../../Assets/Notifications/sounds/isntit.mp3
../../Assets/Notifications/sounds/jinglebellssms.mp3
../../Assets/Notifications/sounds/served.mp3
../../Assets/Notifications/sounds/solemn.mp3

4. Set "Remote Notifications" to enabled in Projucer Android exporter.

To verify that remote notifications are configured properly, go to Remote tab in the demo and press "GetDeviceToken"
button, a dialog with your token (also printed to console in debug build) should show up.


The following steps are only necessary if you have a custom activity defined:

5. Ensure that its launchMode is set to "singleTop" or "singleTask" in Android manifest. This is the default behaviour
   in JUCE so you only need to do it if you have custom Android manifest content. You can do it from Projucer by
   ensuring that "Custom Manifest XML Content" contains:

<manifest>
<application>
<activity android:launchMode="singleTask">
</activity>
</application>
</manifest>

6. Ensure that you override onNewIntent() function in the same way as it is done in JuceActivity.java:

package com.rmsl.juce;

import android.app.Activity;
import android.content.Intent;

//==============================================================================
public class JuceActivity   extends Activity
{
    //==============================================================================
    private native void appNewIntent (Intent intent);

    @Override
    protected void onNewIntent (Intent intent)
    {
        super.onNewIntent (intent);
        setIntent (intent);

        appNewIntent (intent);
    }
}

*/

//==============================================================================
class PushNotificationsDemo final : public Component,
                                    private ChangeListener,
                                    private ComponentListener,
                                    private PushNotifications::Listener
{
public:
    //==============================================================================
    PushNotificationsDemo()
    {
        setupControls();
        distributeControls();

      #if JUCE_PUSH_NOTIFICATIONS
        addAndMakeVisible (headerLabel);
        addAndMakeVisible (mainTabs);
        addAndMakeVisible (sendButton);
      #else
        addAndMakeVisible (notAvailableYetLabel);
      #endif

        headerLabel         .setJustificationType (Justification::centred);
        notAvailableYetLabel.setJustificationType (Justification::centred);

      #if JUCE_MAC
        StringArray tabNames { "Params1", "Params2", "Params3", "Params4" };
      #else
        StringArray tabNames { "Req. params", "Opt. params1", "Opt. params2", "Opt. params3" };
      #endif

        auto colour = getLookAndFeel().findColour (ResizableWindow::backgroundColourId);
        localNotificationsTabs.addTab (tabNames[0], colour, &paramsOneView, false);
        localNotificationsTabs.addTab (tabNames[1], colour, &paramsTwoView, false);
      #if JUCE_ANDROID
        localNotificationsTabs.addTab (tabNames[2], colour, &paramsThreeView, false);
        localNotificationsTabs.addTab (tabNames[3], colour, &paramsFourView,  false);
      #endif
        localNotificationsTabs.addTab ("Aux. actions", colour, &auxActionsView, false);

        mainTabs.addTab ("Local",  colour, &localNotificationsTabs, false);
        mainTabs.addTab ("Remote", colour, &remoteView,             false);

        auto userArea = Desktop::getInstance().getDisplays().getPrimaryDisplay()->userArea;
      #if JUCE_ANDROID || JUCE_IOS
        setSize (userArea.getWidth(), userArea.getHeight());
      #else
        setSize (userArea.getWidth() / 2, userArea.getHeight() / 2);
      #endif

        sendButton.onClick = [this] { sendLocalNotification(); };
        auxActionsView.getDeliveredNotificationsButton .onClick = []
            { PushNotifications::getInstance()->getDeliveredNotifications(); };
        auxActionsView.removeDeliveredNotifWithIdButton.onClick = [this]
            { PushNotifications::getInstance()->removeDeliveredNotification (auxActionsView.deliveredNotifIdentifier.getText()); };
        auxActionsView.removeAllDeliveredNotifsButton  .onClick = []
            { PushNotifications::getInstance()->removeAllDeliveredNotifications(); };
      #if JUCE_IOS || JUCE_MAC
        auxActionsView.getPendingNotificationsButton .onClick = []
            { PushNotifications::getInstance()->getPendingLocalNotifications(); };
        auxActionsView.removePendingNotifWithIdButton.onClick = [this]
            { PushNotifications::getInstance()->removePendingLocalNotification (auxActionsView.pendingNotifIdentifier.getText()); };
        auxActionsView.removeAllPendingNotifsButton  .onClick = []
            { PushNotifications::getInstance()->removeAllPendingLocalNotifications(); };
      #endif

        remoteView.getDeviceTokenButton.onClick = [this]
        {
            String token = PushNotifications::getInstance()->getDeviceToken();

            DBG ("token = " + token);

            if (token.isEmpty())
            {
                showRemoteInstructions();
            }
            else
            {
                auto options = MessageBoxOptions()
                                               .withIconType (MessageBoxIconType::InfoIcon)
                                               .withTitle ("Device token")
                                               .withMessage (token)
                                               .withButton ("OK");
                messageBox = NativeMessageBox::showScopedAsync (options, nullptr);
            }
        };

      #if JUCE_ANDROID
        remoteView.sendRemoteMessageButton.onClick = []
        {
            StringPairArray data;
            data.set ("key1", "value1");
            data.set ("key2", "value2");

            static int id = 100;
            PushNotifications::getInstance()->sendUpstreamMessage ("872047750958",
                                                                   "com.juce.pushnotificationsdemo",
                                                                   String (id++),
                                                                   "standardType",
                                                                   3600,
                                                                   data);
        };

        remoteView.subscribeToSportsButton    .onClick = []
            { PushNotifications::getInstance()->subscribeToTopic ("sports"); };
        remoteView.unsubscribeFromSportsButton.onClick = []
            { PushNotifications::getInstance()->unsubscribeFromTopic ("sports"); };
      #endif

        paramControls.accentColourButton.onClick = [this] { setupAccentColour(); };
        paramControls.ledColourButton   .onClick = [this] { setupLedColour(); };

        jassert (PushNotifications::getInstance()->areNotificationsEnabled());

        PushNotifications::getInstance()->addListener (this);

      #if JUCE_IOS || JUCE_MAC
        paramControls.fireInComboBox.onChange = [this] { delayNotification(); };
        PushNotifications::getInstance()->requestPermissionsWithSettings (getNotificationSettings());
      #elif JUCE_ANDROID
        PushNotifications::ChannelGroup cg { "demoGroup", "demo group" };
        PushNotifications::getInstance()->setupChannels ({ { cg } }, getAndroidChannels());
      #endif

       #if JUCE_IOS || JUCE_ANDROID
        setPortraitOrientationEnabled (true);
       #endif
    }

    ~PushNotificationsDemo() override
    {
        PushNotifications::getInstance()->removeListener (this);

       #if JUCE_IOS || JUCE_ANDROID
        setPortraitOrientationEnabled (false);
       #endif
    }

    void setPortraitOrientationEnabled (bool shouldBeEnabled)
    {
        auto allowedOrientations = Desktop::getInstance().getOrientationsEnabled();

        if (shouldBeEnabled)
            allowedOrientations |= Desktop::upright;
        else
            allowedOrientations &= ~Desktop::upright;

        Desktop::getInstance().setOrientationsEnabled (allowedOrientations);
    }

    void paint (Graphics& g) override
    {
        g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
    }

    void resized() override
    {
        auto bounds = getLocalBounds().reduced (getWidth() / 20, getHeight() / 40);

        headerLabel.setBounds (bounds.removeFromTop (bounds.proportionOfHeight (0.1f)));
        mainTabs   .setBounds (bounds.removeFromTop (bounds.proportionOfHeight (0.8f)));
        sendButton .setBounds (bounds);

        notAvailableYetLabel.setBounds (getLocalBounds());
    }

private:
    void delayNotification()
    {
        auto repeatsAllowed = paramControls.fireInComboBox.getSelectedItemIndex() >= 6;

        paramControls.repeatButton.setEnabled (repeatsAllowed);

        if (! repeatsAllowed)
            paramControls.repeatButton.setToggleState (false, NotificationType::sendNotification);
    }

    void sendLocalNotification()
    {
        PushNotifications::Notification n;

        fillRequiredParams (n);
        fillOptionalParamsOne (n);
      #if JUCE_ANDROID
        fillOptionalParamsTwo (n);
        fillOptionalParamsThree (n);
      #endif

        if (! n.isValid())
        {
          #if JUCE_IOS
            String requiredFields = "identifier, title, body and category";
          #elif JUCE_ANDROID
            String requiredFields = "channel ID, title, body and icon";
          #else
            String requiredFields = "all required fields";
          #endif

            auto options = MessageBoxOptions()
                                           .withIconType (MessageBoxIconType::InfoIcon)
                                           .withTitle ("Incorrect notifications setup")
                                           .withMessage ("Please make sure that " + requiredFields + " are set.")
                                           .withButton ("OK");
            messageBox = NativeMessageBox::showScopedAsync (options, nullptr);

            return;
        }

        PushNotifications::getInstance()->sendLocalNotification (n);
    }

    void fillRequiredParams      (PushNotifications::Notification& n)
    {
        n.identifier = paramControls.identifierEditor.getText();
        n.title      = paramControls.titleEditor     .getText();
        n.body       = paramControls.bodyEditor      .getText();
      #if JUCE_IOS
        n.category = paramControls.categoryComboBox.getText();
      #elif JUCE_ANDROID || JUCE_MAC
       #if JUCE_MAC
        String prefix = "Notifications/images/";
        String extension = ".png";
       #else
        String prefix;
        String extension;
       #endif
        if (paramControls.iconComboBox.getSelectedItemIndex() == 0)
            n.icon = prefix + "ic_stat_name" + extension;
        else if (paramControls.iconComboBox.getSelectedItemIndex() == 1)
            n.icon = prefix + "ic_stat_name2" + extension;
        else if (paramControls.iconComboBox.getSelectedItemIndex() == 2)
            n.icon = prefix + "ic_stat_name3" + extension;
        else if (paramControls.iconComboBox.getSelectedItemIndex() == 3)
            n.icon = prefix + "ic_stat_name4" + extension;
        else if (paramControls.iconComboBox.getSelectedItemIndex() == 4)
            n.icon = prefix + "ic_stat_name5" + extension;
      #endif

      #if JUCE_ANDROID
        // Note: this is not strictly speaking required param, just doing it here because it is the fastest way!
        n.publicVersion.reset (new PushNotifications::Notification());
        n.publicVersion->identifier = "blahblahblah";
        n.publicVersion->title      = "Public title!";
        n.publicVersion->body       = "Public body!";
        n.publicVersion->icon       = n.icon;

        n.channelId = String (paramControls.channelIdComboBox.getSelectedItemIndex() + 1);
      #endif
    }

    void fillOptionalParamsOne   (PushNotifications::Notification& n)
    {
        n.subtitle = paramControls.subtitleEditor.getText();
        n.badgeNumber = paramControls.badgeNumberComboBox.getSelectedItemIndex();

        if (paramControls.soundToPlayComboBox.getSelectedItemIndex() > 0)
            n.soundToPlay = URL (paramControls.soundToPlayComboBox.getItemText (paramControls.soundToPlayComboBox.getSelectedItemIndex()));

        n.properties = JSON::parse (paramControls.propertiesEditor.getText());

      #if JUCE_IOS || JUCE_MAC
        n.triggerIntervalSec = double (paramControls.fireInComboBox.getSelectedItemIndex() * 10);
        n.repeat = paramControls.repeatButton.getToggleState();
      #elif JUCE_ANDROID
        if (paramControls.largeIconComboBox.getSelectedItemIndex() == 1)
            n.largeIcon = getImageFromAssets ("Notifications/images/ic_stat_name6.png");
        else if (paramControls.largeIconComboBox.getSelectedItemIndex() == 2)
            n.largeIcon = getImageFromAssets ("Notifications/images/ic_stat_name7.png");
        else if (paramControls.largeIconComboBox.getSelectedItemIndex() == 3)
            n.largeIcon = getImageFromAssets ("Notifications/images/ic_stat_name8.png");
        else if (paramControls.largeIconComboBox.getSelectedItemIndex() == 4)
            n.largeIcon = getImageFromAssets ("Notifications/images/ic_stat_name9.png");
        else if (paramControls.largeIconComboBox.getSelectedItemIndex() == 5)
            n.largeIcon = getImageFromAssets ("Notifications/images/ic_stat_name10.png");

        n.badgeIconType = (PushNotifications::Notification::BadgeIconType) paramControls.badgeIconComboBox.getSelectedItemIndex();
        n.tickerText  = paramControls.tickerTextEditor.getText();

        n.shouldAutoCancel = paramControls.autoCancelButton   .getToggleState();
        n.alertOnlyOnce    = paramControls.alertOnlyOnceButton.getToggleState();
      #endif

      #if JUCE_ANDROID || JUCE_MAC
        if (paramControls.actionsComboBox.getSelectedItemIndex() == 1)
        {
            PushNotifications::Notification::Action a, a2;
            a .style = PushNotifications::Notification::Action::button;
            a2.style = PushNotifications::Notification::Action::button;
            a .title = a .identifier = "Ok";
            a2.title = a2.identifier = "Cancel";
            n.actions.add (a);
            n.actions.add (a2);
        }
        else if (paramControls.actionsComboBox.getSelectedItemIndex() == 2)
        {
            PushNotifications::Notification::Action a, a2;
            a .title = a .identifier = "Input Text Here";
            a2.title = a2.identifier = "No";
            a .style = PushNotifications::Notification::Action::text;
            a2.style = PushNotifications::Notification::Action::button;
            a .icon = "ic_stat_name4";
            a2.icon = "ic_stat_name5";
            a.textInputPlaceholder = "placeholder text ...";
            n.actions.add (a);
            n.actions.add (a2);
        }
        else if (paramControls.actionsComboBox.getSelectedItemIndex() == 3)
        {
            PushNotifications::Notification::Action a, a2;
            a .title = a .identifier = "Ok";
            a2.title = a2.identifier = "Cancel";
            a .style = PushNotifications::Notification::Action::button;
            a2.style = PushNotifications::Notification::Action::button;
            a .icon = "ic_stat_name4";
            a2.icon = "ic_stat_name5";
            n.actions.add (a);
            n.actions.add (a2);
        }
        else if (paramControls.actionsComboBox.getSelectedItemIndex() == 4)
        {
            PushNotifications::Notification::Action a, a2;
            a .title = a .identifier = "Input Text Here";
            a2.title = a2.identifier = "No";
            a .style = PushNotifications::Notification::Action::text;
            a2.style = PushNotifications::Notification::Action::button;
            a .icon = "ic_stat_name4";
            a2.icon = "ic_stat_name5";
            a.textInputPlaceholder = "placeholder text ...";
            a.allowedResponses.add ("Response 1");
            a.allowedResponses.add ("Response 2");
            a.allowedResponses.add ("Response 3");
            n.actions.add (a);
            n.actions.add (a2);
        }
      #endif
    }

    void fillOptionalParamsTwo   (PushNotifications::Notification& n)
    {
        using Notification = PushNotifications::Notification;

        Notification::Progress progress;
        progress.max           = paramControls.progressMaxComboBox    .getSelectedItemIndex() * 10;
        progress.current       = paramControls.progressCurrentComboBox.getSelectedItemIndex() * 10;
        progress.indeterminate = paramControls.progressIndeterminateButton.getToggleState();

        n.progress = progress;
        n.person   = paramControls.personEditor.getText();
        n.type                 = Notification::Type                 (paramControls.categoryComboBox            .getSelectedItemIndex());
        n.priority             = Notification::Priority             (paramControls.priorityComboBox            .getSelectedItemIndex() - 2);
        n.lockScreenAppearance = Notification::LockScreenAppearance (paramControls.lockScreenVisibilityComboBox.getSelectedItemIndex() - 1);
        n.groupId      = paramControls.groupIdEditor.getText();
        n.groupSortKey = paramControls.sortKeyEditor.getText();
        n.groupSummary = paramControls.groupSummaryButton.getToggleState();
        n.groupAlertBehaviour = Notification::GroupAlertBehaviour (paramControls.groupAlertBehaviourComboBox.getSelectedItemIndex());
    }

    void fillOptionalParamsThree (PushNotifications::Notification& n)
    {
        n.accentColour = paramControls.accentColourButton.findColour (TextButton::buttonColourId, false);
        n.ledColour    = paramControls.ledColourButton   .findColour (TextButton::buttonColourId, false);

        using Notification = PushNotifications::Notification;
        Notification::LedBlinkPattern ledBlinkPattern;
        ledBlinkPattern.msToBeOn  = paramControls.ledMsToBeOnComboBox .getSelectedItemIndex() * 200;
        ledBlinkPattern.msToBeOff = paramControls.ledMsToBeOffComboBox.getSelectedItemIndex() * 200;
        n.ledBlinkPattern = ledBlinkPattern;

        Array<int> vibrationPattern;

        if (paramControls.vibratorMsToBeOnComboBox .getSelectedItemIndex() > 0 &&
            paramControls.vibratorMsToBeOffComboBox.getSelectedItemIndex() > 0)
        {
            vibrationPattern.add     (paramControls.vibratorMsToBeOffComboBox.getSelectedItemIndex() * 500);
            vibrationPattern.add     (paramControls.vibratorMsToBeOnComboBox .getSelectedItemIndex() * 500);
            vibrationPattern.add (2 * paramControls.vibratorMsToBeOffComboBox.getSelectedItemIndex() * 500);
            vibrationPattern.add (2 * paramControls.vibratorMsToBeOnComboBox .getSelectedItemIndex() * 500);
        }

        n.vibrationPattern = vibrationPattern;

        n.localOnly = paramControls.localOnlyButton.getToggleState();
        n.ongoing = paramControls.ongoingButton.getToggleState();
        n.timestampVisibility = Notification::TimestampVisibility (paramControls.timestampVisibilityComboBox.getSelectedItemIndex());

        if (paramControls.timeoutAfterComboBox.getSelectedItemIndex() > 0)
        {
            auto index = paramControls.timeoutAfterComboBox.getSelectedItemIndex();
            n.timeoutAfterMs = index * 1000 + 4000;
        }
    }

    void setupAccentColour()
    {
        auto accentColourSelector = std::make_unique<ColourSelector>();

        accentColourSelector->setName ("accent colour");
        accentColourSelector->setCurrentColour (paramControls.accentColourButton.findColour (TextButton::buttonColourId));
        accentColourSelector->setColour (ColourSelector::backgroundColourId, Colours::transparentBlack);
        accentColourSelector->setSize (200, 200);
        accentColourSelector->addComponentListener (this);
        accentColourSelector->addChangeListener (this);

        paramControls.accentColourSelector = accentColourSelector.get();

        CallOutBox::launchAsynchronously (std::move (accentColourSelector), paramControls.accentColourButton.getScreenBounds(), nullptr);
    }

    void setupLedColour()
    {
        auto ledColourSelector = std::make_unique<ColourSelector>();

        ledColourSelector->setName ("led colour");
        ledColourSelector->setCurrentColour (paramControls.ledColourButton.findColour (TextButton::buttonColourId));
        ledColourSelector->setColour (ColourSelector::backgroundColourId, Colours::transparentBlack);
        ledColourSelector->setSize (200, 200);
        ledColourSelector->addComponentListener (this);
        ledColourSelector->addChangeListener (this);

        paramControls.ledColourSelector = ledColourSelector.get();

        CallOutBox::launchAsynchronously (std::move (ledColourSelector), paramControls.accentColourButton.getScreenBounds(), nullptr);
    }

    void changeListenerCallback (ChangeBroadcaster* source) override
    {
        if (source == paramControls.accentColourSelector)
        {
            auto c = paramControls.accentColourSelector->getCurrentColour();
            paramControls.accentColourButton.setColour (TextButton::buttonColourId, c);
        }
        else if (source == paramControls.ledColourSelector)
        {
            auto c = paramControls.ledColourSelector->getCurrentColour();
            paramControls.ledColourButton.setColour (TextButton::buttonColourId, c);
        }
    }

    void componentBeingDeleted (Component& component) override
    {
        if (&component == paramControls.accentColourSelector)
            paramControls.accentColourSelector = nullptr;
        else if (&component == paramControls.ledColourSelector)
            paramControls.ledColourSelector = nullptr;
    }

    void handleNotification (bool isLocalNotification, const PushNotifications::Notification& n) override
    {
        ignoreUnused (isLocalNotification);

        auto options = MessageBoxOptions()
                                       .withIconType (MessageBoxIconType::InfoIcon)
                                       .withTitle ("Received notification")
                                       .withMessage ("ID: " + n.identifier
                                                     + ", title: " + n.title
                                                     + ", body: " + n.body)
                                       .withButton ("OK");
        messageBox = NativeMessageBox::showScopedAsync (options, nullptr);
    }

    void handleNotificationAction (bool isLocalNotification,
                                   const PushNotifications::Notification& n,
                                   const juce::String& actionIdentifier,
                                   const juce::String& optionalResponse) override
    {
        ignoreUnused (isLocalNotification);

        auto options = MessageBoxOptions()
                                       .withIconType (MessageBoxIconType::InfoIcon)
                                       .withTitle ("Received notification action")
                                       .withMessage ("ID: " + n.identifier
                                                     + ", title: " + n.title
                                                     + ", body: " + n.body
                                                     + ", action: " + actionIdentifier
                                                     + ", optionalResponse: " + optionalResponse)
                                       .withButton ("OK");
        messageBox = NativeMessageBox::showScopedAsync (options, nullptr);

        PushNotifications::getInstance()->removeDeliveredNotification (n.identifier);
    }

    void localNotificationDismissedByUser (const PushNotifications::Notification& n) override
    {
        auto options = MessageBoxOptions()
                                       .withIconType (MessageBoxIconType::InfoIcon)
                                       .withTitle ("Notification dismissed by a user")
                                       .withMessage ("ID: " + n.identifier
                                                     + ", title: " + n.title
                                                     + ", body: " + n.body)
                                       .withButton ("OK");
        messageBox = NativeMessageBox::showScopedAsync (options, nullptr);
    }

    void deliveredNotificationsListReceived (const Array<PushNotifications::Notification>& notifs) override
    {
        String text = "Received notifications: ";

        for (auto& n : notifs)
            text << "(" << n.identifier << ", " << n.title << ", " << n.body << "), ";

        auto options = MessageBoxOptions()
                                       .withIconType (MessageBoxIconType::InfoIcon)
                                       .withTitle ("Received notification list")
                                       .withMessage (text)
                                       .withButton ("OK");
        messageBox = NativeMessageBox::showScopedAsync (options, nullptr);
    }

    void pendingLocalNotificationsListReceived (const Array<PushNotifications::Notification>& notifs) override
    {
        String text = "Pending notifications: ";

        for (auto& n : notifs)
            text << "(" << n.identifier << ", " << n.title << ", " << n.body << "), ";

        auto options = MessageBoxOptions()
                                       .withIconType (MessageBoxIconType::InfoIcon)
                                       .withTitle ("Pending notification list")
                                       .withMessage (text)
                                       .withButton ("OK");
        messageBox = NativeMessageBox::showScopedAsync (options, nullptr);
    }

    void deviceTokenRefreshed (const String& token) override
    {
        auto options = MessageBoxOptions()
                                       .withIconType (MessageBoxIconType::InfoIcon)
                                       .withTitle ("Device token refreshed")
                                       .withMessage (token)
                                       .withButton ("OK");
        messageBox = NativeMessageBox::showScopedAsync (options, nullptr);
    }

  #if JUCE_ANDROID
    void remoteNotificationsDeleted() override
    {
        auto options = MessageBoxOptions()
                                       .withIconType (MessageBoxIconType::InfoIcon)
                                       .withTitle ("Remote notifications deleted")
                                       .withMessage ("Some of the pending messages were removed!")
                                       .withButton ("OK");
        messageBox = NativeMessageBox::showScopedAsync (options, nullptr);
    }

    void upstreamMessageSent (const String& messageId) override
    {
        auto options = MessageBoxOptions()
                                       .withIconType (MessageBoxIconType::InfoIcon)
                                       .withTitle ("Upstream message sent")
                                       .withMessage ("Message id: " + messageId)
                                       .withButton ("OK");
        messageBox = NativeMessageBox::showScopedAsync (options, nullptr);
    }

    void upstreamMessageSendingError (const String& messageId, const String& error) override
    {
        auto options = MessageBoxOptions()
                                       .withIconType (MessageBoxIconType::InfoIcon)
                                       .withTitle ("Upstream message sending error")
                                       .withMessage ("Message id: " + messageId
                                                     + "\nerror: " + error)
                                       .withButton ("OK");
        messageBox = NativeMessageBox::showScopedAsync (options, nullptr);
    }

    static Array<PushNotifications::Channel> getAndroidChannels()
    {
        using Channel = PushNotifications::Channel;

        Channel ch1, ch2, ch3;

        ch1.identifier = "1";
        ch1.name = "HighImportance";
        ch1.importance = PushNotifications::Channel::max;
        ch1.lockScreenAppearance = PushNotifications::Notification::showCompletely;
        ch1.description = "High Priority Channel for important stuff";
        ch1.groupId = "demoGroup";
        ch1.ledColour = Colours::red;
        ch1.bypassDoNotDisturb = true;
        ch1.canShowBadge = true;
        ch1.enableLights = true;
        ch1.enableVibration = true;
        ch1.soundToPlay = URL ("demonstrative");
        ch1.vibrationPattern = { 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200 };

        ch2.identifier = "2";
        ch2.name = "MediumImportance";
        ch2.importance = PushNotifications::Channel::normal;
        ch2.lockScreenAppearance = PushNotifications::Notification::showPartially;
        ch2.description = "Medium Priority Channel for standard stuff";
        ch2.groupId = "demoGroup";
        ch2.ledColour = Colours::yellow;
        ch2.canShowBadge = true;
        ch2.enableLights = true;
        ch2.enableVibration = true;
        ch2.soundToPlay = URL ("default_os_sound");
        ch2.vibrationPattern = { 1000, 1000 };

        ch3.identifier = "3";
        ch3.name = "LowImportance";
        ch3.importance = PushNotifications::Channel::min;
        ch3.lockScreenAppearance = PushNotifications::Notification::dontShow;
        ch3.description = "Low Priority Channel for silly stuff";
        ch3.groupId = "demoGroup";

        return { ch1, ch2, ch3 };
    }

  #elif JUCE_IOS || JUCE_MAC
    static PushNotifications::Settings getNotificationSettings()
    {
        PushNotifications::Settings settings;
        settings.allowAlert = true;
        settings.allowBadge = true;
        settings.allowSound = true;

      #if JUCE_IOS
        using Action   = PushNotifications::Settings::Action;
        using Category = PushNotifications::Settings::Category;

        Action okAction;
        okAction.identifier = "okAction";
        okAction.title = "OK!";
        okAction.style = Action::button;
        okAction.triggerInBackground = true;

        Action cancelAction;
        cancelAction.identifier = "cancelAction";
        cancelAction.title = "Cancel";
        cancelAction.style = Action::button;
        cancelAction.triggerInBackground = true;
        cancelAction.destructive = true;

        Action textAction;
        textAction.identifier = "textAction";
        textAction.title = "Enter text";
        textAction.style = Action::text;
        textAction.triggerInBackground = true;
        textAction.destructive = false;
        textAction.textInputButtonText = "Ok";
        textAction.textInputPlaceholder = "Enter text...";

        Category okCategory;
        okCategory.identifier = "okCategory";
        okCategory.actions = { okAction };

        Category okCancelCategory;
        okCancelCategory.identifier = "okCancelCategory";
        okCancelCategory.actions = { okAction, cancelAction };

        Category textCategory;
        textCategory.identifier = "textCategory";
        textCategory.actions = { textAction };
        textCategory.sendDismissAction = true;

        settings.categories = { okCategory, okCancelCategory, textCategory };
      #endif

        return settings;
    }
  #endif

    struct RowComponent final : public Component
    {
        RowComponent (Label& l, Component& c, int u = 1)
            : label (l),
              editor (c),
              rowUnits (u)
        {
            addAndMakeVisible (label);
            addAndMakeVisible (editor);
        }

        void resized() override
        {
            auto bounds = getLocalBounds();
            label .setBounds (bounds.removeFromLeft (getWidth() / 3));
            editor.setBounds (bounds);
        }

        Label& label;
        Component& editor;
        int rowUnits;
    };

    struct ParamControls
    {
        Label        identifierLabel            { "identifierLabel",            "Identifier" };
        TextEditor   identifierEditor;
        Label        titleLabel                 { "titleLabel",                 "Title" };
        TextEditor   titleEditor;
        Label        bodyLabel                  { "bodyLabel",                  "Body" };
        TextEditor   bodyEditor;

        Label        categoryLabel              { "categoryLabel",              "Category" };
        ComboBox     categoryComboBox;
        Label        channelIdLabel             { "channelIdLabel",             "Channel ID" };
        ComboBox     channelIdComboBox;
        Label        iconLabel                  { "iconLabel",                  "Icon" };
        ComboBox     iconComboBox;

        Label        subtitleLabel              { "subtitleLabel",              "Subtitle" };
        TextEditor   subtitleEditor;
        Label        badgeNumberLabel           { "badgeNumberLabel",           "BadgeNumber" };
        ComboBox     badgeNumberComboBox;
        Label        soundToPlayLabel           { "soundToPlayLabel",           "SoundToPlay" };
        ComboBox     soundToPlayComboBox;
        Label        propertiesLabel            { "propertiesLabel",            "Properties" };
        TextEditor   propertiesEditor;
        Label        fireInLabel                { "fireInLabel",                "Fire in" };
        ComboBox     fireInComboBox;
        Label        repeatLabel                { "repeatLabel",                "Repeat" };
        ToggleButton repeatButton;
        Label        largeIconLabel             { "largeIconLabel",             "Large Icon" };
        ComboBox     largeIconComboBox;
        Label        badgeIconLabel             { "badgeIconLabel",             "Badge Icon" };
        ComboBox     badgeIconComboBox;
        Label        tickerTextLabel            { "tickerTextLabel",            "Ticker Text" };
        TextEditor   tickerTextEditor;
        Label        autoCancelLabel            { "autoCancelLabel",            "AutoCancel" };
        ToggleButton autoCancelButton;
        Label        alertOnlyOnceLabel         { "alertOnlyOnceLabel",         "AlertOnlyOnce" };
        ToggleButton alertOnlyOnceButton;
        Label        actionsLabel               { "actionsLabel",               "Actions" };
        ComboBox     actionsComboBox;

        Label        progressMaxLabel           { "progressMaxLabel",           "ProgressMax" };
        ComboBox     progressMaxComboBox;
        Label        progressCurrentLabel       { "progressCurrentLabel",       "ProgressCurrent" };
        ComboBox     progressCurrentComboBox;
        Label        progressIndeterminateLabel { "progressIndeterminateLabel", "ProgressIndeterminate" };
        ToggleButton progressIndeterminateButton;
        Label        notifCategoryLabel         { "notifCategoryLabel",         "Category" };
        ComboBox     notifCategoryComboBox;
        Label        priorityLabel              { "priorityLabel",              "Priority" };
        ComboBox     priorityComboBox;
        Label        personLabel                { "personLabel",                "Person" };
        TextEditor   personEditor;
        Label        lockScreenVisibilityLabel  { "lockScreenVisibilityLabel",  "LockScreenVisibility" };
        ComboBox     lockScreenVisibilityComboBox;
        Label        groupIdLabel               { "groupIdLabel",               "GroupID" };
        TextEditor   groupIdEditor;
        Label        sortKeyLabel               { "sortKeyLabel",               "SortKey" };
        TextEditor   sortKeyEditor;
        Label        groupSummaryLabel          { "groupSummaryLabel",          "GroupSummary" };
        ToggleButton groupSummaryButton;
        Label        groupAlertBehaviourLabel   { "groupAlertBehaviourLabel",   "GroupAlertBehaviour" };
        ComboBox     groupAlertBehaviourComboBox;

        Label        accentColourLabel          { "accentColourLabel",          "AccentColour" };
        TextButton   accentColourButton;
        Label        ledColourLabel             { "ledColourLabel",             "LedColour" };
        TextButton   ledColourButton;
        Label        ledMsToBeOnLabel           { "ledMsToBeOnLabel",           "LedMsToBeOn" };
        ComboBox     ledMsToBeOnComboBox;
        Label        ledMsToBeOffLabel          { "ledMsToBeOffLabel",          "LedMsToBeOff" };
        ComboBox     ledMsToBeOffComboBox;
        Label        vibratorMsToBeOnLabel      { "vibratorMsToBeOnLabel",      "VibrationMsToBeOn" };
        ComboBox     vibratorMsToBeOnComboBox;
        Label        vibratorMsToBeOffLabel     { "vibratorMsToBeOffLabel",     "VibrationMsToBeOff" };
        ComboBox     vibratorMsToBeOffComboBox;
        Label        localOnlyLabel             { "localOnlyLabel",             "LocalOnly" };
        ToggleButton localOnlyButton;
        Label        ongoingLabel               { "ongoingLabel",               "Ongoing" };
        ToggleButton ongoingButton;
        Label        timestampVisibilityLabel   { "timestampVisibilityLabel",   "TimestampMode" };
        ComboBox     timestampVisibilityComboBox;
        Label        timeoutAfterLabel          { "timeoutAfterLabel",          "Timeout After Ms" };
        ComboBox     timeoutAfterComboBox;

        ColourSelector* accentColourSelector = nullptr;
        ColourSelector* ledColourSelector    = nullptr;
    };

    void setupControls()
    {
        auto& pc = paramControls;

        StringArray categories { "okCategory", "okCancelCategory", "textCategory" };

        for (auto& c : categories)
            pc.categoryComboBox.addItem (c, pc.categoryComboBox.getNumItems() + 1);
        pc.categoryComboBox.setSelectedItemIndex (0);

        for (auto i = 1; i <= 3; ++i)
            pc.channelIdComboBox.addItem (String (i), i);
        pc.channelIdComboBox.setSelectedItemIndex (0);

        for (auto i = 0; i < 5; ++i)
            pc.iconComboBox.addItem ("icon" + String (i + 1), i + 1);
        pc.iconComboBox.setSelectedItemIndex (0);

      #if JUCE_MAC
        pc.iconComboBox.addItem ("none", 100);
      #endif

        pc.fireInComboBox.addItem ("Now", 1);

        for (auto i = 1; i < 11; ++i)
            pc.fireInComboBox.addItem (String (10 * i) + "seconds", i + 1);
        pc.fireInComboBox.setSelectedItemIndex (0);

        pc.largeIconComboBox.addItem ("none", 1);

        for (auto i = 1; i < 5; ++i)
            pc.largeIconComboBox.addItem ("icon" + String (i), i + 1);
        pc.largeIconComboBox.setSelectedItemIndex (0);

        pc.badgeIconComboBox.addItem ("none",  1);
        pc.badgeIconComboBox.addItem ("small", 2);
        pc.badgeIconComboBox.addItem ("large", 3);
        pc.badgeIconComboBox.setSelectedItemIndex (2);

        pc.actionsComboBox.addItem ("none",       1);
        pc.actionsComboBox.addItem ("ok-cancel",  2);
        pc.actionsComboBox.addItem ("text-input", 3);
      #if JUCE_ANDROID
        pc.actionsComboBox.addItem ("ok-cancel-icons", 4);
        pc.actionsComboBox.addItem ("text-input-limited_responses", 5);
      #endif
        pc.actionsComboBox.setSelectedItemIndex (0);

        for (auto i = 0; i < 7; ++i)
            pc.badgeNumberComboBox.addItem (String (i), i + 1);
        pc.badgeNumberComboBox.setSelectedItemIndex (0);

      #if JUCE_IOS
        String prefix = "Notifications/sounds/";
        String extension = ".caf";
      #else
        String prefix;
        String extension;
      #endif

        pc.soundToPlayComboBox.addItem ("none", 1);
        pc.soundToPlayComboBox.addItem ("default_os_sound", 2);
        pc.soundToPlayComboBox.addItem (prefix + "demonstrative"  + extension, 3);
        pc.soundToPlayComboBox.addItem (prefix + "isntit"         + extension, 4);
        pc.soundToPlayComboBox.addItem (prefix + "jinglebellssms" + extension, 5);
        pc.soundToPlayComboBox.addItem (prefix + "served"         + extension, 6);
        pc.soundToPlayComboBox.addItem (prefix + "solemn"         + extension, 7);
        pc.soundToPlayComboBox.setSelectedItemIndex (1);

        for (auto i = 0; i < 11; ++i)
        {
            pc.progressMaxComboBox    .addItem (String (i * 10) + "%", i + 1);
            pc.progressCurrentComboBox.addItem (String (i * 10) + "%", i + 1);
        }

        pc.progressMaxComboBox    .setSelectedItemIndex (0);
        pc.progressCurrentComboBox.setSelectedItemIndex (0);

        pc.notifCategoryComboBox.addItem ("unspecified",     1);
        pc.notifCategoryComboBox.addItem ("alarm",           2);
        pc.notifCategoryComboBox.addItem ("call",            3);
        pc.notifCategoryComboBox.addItem ("email",           4);
        pc.notifCategoryComboBox.addItem ("error",           5);
        pc.notifCategoryComboBox.addItem ("event",           6);
        pc.notifCategoryComboBox.addItem ("message",         7);
        pc.notifCategoryComboBox.addItem ("progress",        8);
        pc.notifCategoryComboBox.addItem ("promo",           9);
        pc.notifCategoryComboBox.addItem ("recommendation", 10);
        pc.notifCategoryComboBox.addItem ("reminder",       11);
        pc.notifCategoryComboBox.addItem ("service",        12);
        pc.notifCategoryComboBox.addItem ("social",         13);
        pc.notifCategoryComboBox.addItem ("status",         14);
        pc.notifCategoryComboBox.addItem ("system",         15);
        pc.notifCategoryComboBox.addItem ("transport",      16);
        pc.notifCategoryComboBox.setSelectedItemIndex (0);

        for (auto i = -2; i < 3; ++i)
            pc.priorityComboBox.addItem (String (i), i + 3);
        pc.priorityComboBox.setSelectedItemIndex (2);

        pc.lockScreenVisibilityComboBox.addItem ("don't show",      1);
        pc.lockScreenVisibilityComboBox.addItem ("show partially",  2);
        pc.lockScreenVisibilityComboBox.addItem ("show completely", 3);
        pc.lockScreenVisibilityComboBox.setSelectedItemIndex (1);

        pc.groupAlertBehaviourComboBox.addItem ("alert all",      1);
        pc.groupAlertBehaviourComboBox.addItem ("alert summary",  2);
        pc.groupAlertBehaviourComboBox.addItem ("alert children", 3);
        pc.groupAlertBehaviourComboBox.setSelectedItemIndex (0);

        pc.timeoutAfterComboBox.addItem ("No timeout", 1);

        for (auto i = 0; i < 10; ++i)
        {
            pc.ledMsToBeOnComboBox      .addItem (String (i * 200) + "ms", i + 1);
            pc.ledMsToBeOffComboBox     .addItem (String (i * 200) + "ms", i + 1);
            pc.vibratorMsToBeOnComboBox .addItem (String (i * 500) + "ms", i + 1);
            pc.vibratorMsToBeOffComboBox.addItem (String (i * 500) + "ms", i + 1);
            pc.timeoutAfterComboBox     .addItem (String (5000 + 1000 * i) + "ms", i + 2);
        }

        pc.ledMsToBeOnComboBox      .setSelectedItemIndex (5);
        pc.ledMsToBeOffComboBox     .setSelectedItemIndex (5);
        pc.vibratorMsToBeOnComboBox .setSelectedItemIndex (0);
        pc.vibratorMsToBeOffComboBox.setSelectedItemIndex (0);
        pc.timeoutAfterComboBox     .setSelectedItemIndex (0);

        pc.timestampVisibilityComboBox.addItem ("off",         1);
        pc.timestampVisibilityComboBox.addItem ("on",          2);
        pc.timestampVisibilityComboBox.addItem ("chronometer", 3);
        pc.timestampVisibilityComboBox.addItem ("count down",  4);
        pc.timestampVisibilityComboBox.setSelectedItemIndex (1);
    }

    void distributeControls()
    {
        auto& pc = paramControls;

        paramsOneView  .addRowComponent (new RowComponent (pc.identifierLabel,            pc.identifierEditor));
        paramsOneView  .addRowComponent (new RowComponent (pc.titleLabel,                 pc.titleEditor));
        paramsOneView  .addRowComponent (new RowComponent (pc.bodyLabel,                  pc.bodyEditor, 4));
      #if JUCE_IOS
        paramsOneView  .addRowComponent (new RowComponent (pc.categoryLabel,              pc.categoryComboBox));
      #elif JUCE_ANDROID
        paramsOneView  .addRowComponent (new RowComponent (pc.channelIdLabel,             pc.channelIdComboBox));
      #endif
      #if JUCE_ANDROID || JUCE_MAC
        paramsOneView  .addRowComponent (new RowComponent (pc.iconLabel,                  pc.iconComboBox));
      #endif

        paramsTwoView  .addRowComponent (new RowComponent (pc.subtitleLabel,              pc.subtitleEditor));
      #if ! JUCE_MAC
        paramsTwoView  .addRowComponent (new RowComponent (pc.badgeNumberLabel,           pc.badgeNumberComboBox));
      #endif
        paramsTwoView  .addRowComponent (new RowComponent (pc.soundToPlayLabel,           pc.soundToPlayComboBox));
        paramsTwoView  .addRowComponent (new RowComponent (pc.propertiesLabel,            pc.propertiesEditor, 3));
      #if JUCE_IOS || JUCE_MAC
        paramsTwoView  .addRowComponent (new RowComponent (pc.fireInLabel,                pc.fireInComboBox));
        paramsTwoView  .addRowComponent (new RowComponent (pc.repeatLabel,                pc.repeatButton));
      #elif JUCE_ANDROID
        paramsTwoView  .addRowComponent (new RowComponent (pc.largeIconLabel,             pc.largeIconComboBox));
        paramsTwoView  .addRowComponent (new RowComponent (pc.badgeIconLabel,             pc.badgeIconComboBox));
        paramsTwoView  .addRowComponent (new RowComponent (pc.tickerTextLabel,            pc.tickerTextEditor));
        paramsTwoView  .addRowComponent (new RowComponent (pc.autoCancelLabel,            pc.autoCancelButton));
        paramsTwoView  .addRowComponent (new RowComponent (pc.alertOnlyOnceLabel,         pc.alertOnlyOnceButton));
      #endif
      #if JUCE_ANDROID || JUCE_MAC
        paramsTwoView  .addRowComponent (new RowComponent (pc.actionsLabel,               pc.actionsComboBox));
      #endif
      #if JUCE_ANDROID
        paramsThreeView.addRowComponent (new RowComponent (pc.progressMaxLabel,           pc.progressMaxComboBox));
        paramsThreeView.addRowComponent (new RowComponent (pc.progressCurrentLabel,       pc.progressCurrentComboBox));
        paramsThreeView.addRowComponent (new RowComponent (pc.progressIndeterminateLabel, pc.progressIndeterminateButton));
        paramsThreeView.addRowComponent (new RowComponent (pc.categoryLabel,              pc.categoryComboBox));
        paramsThreeView.addRowComponent (new RowComponent (pc.priorityLabel,              pc.priorityComboBox));
        paramsThreeView.addRowComponent (new RowComponent (pc.personLabel,                pc.personEditor));
        paramsThreeView.addRowComponent (new RowComponent (pc.lockScreenVisibilityLabel,  pc.lockScreenVisibilityComboBox));
        paramsThreeView.addRowComponent (new RowComponent (pc.groupIdLabel,               pc.groupIdEditor));
        paramsThreeView.addRowComponent (new RowComponent (pc.sortKeyLabel,               pc.sortKeyEditor));
        paramsThreeView.addRowComponent (new RowComponent (pc.groupSummaryLabel,          pc.groupSummaryButton));
        paramsThreeView.addRowComponent (new RowComponent (pc.groupAlertBehaviourLabel,   pc.groupAlertBehaviourComboBox));
        paramsFourView .addRowComponent (new RowComponent (pc.accentColourLabel,          pc.accentColourButton));
        paramsFourView .addRowComponent (new RowComponent (pc.ledColourLabel,             pc.ledColourButton));
        paramsFourView .addRowComponent (new RowComponent (pc.ledMsToBeOffLabel,          pc.ledMsToBeOffComboBox));
        paramsFourView .addRowComponent (new RowComponent (pc.ledMsToBeOnLabel,           pc.ledMsToBeOnComboBox));
        paramsFourView .addRowComponent (new RowComponent (pc.vibratorMsToBeOffLabel,     pc.vibratorMsToBeOffComboBox));
        paramsFourView .addRowComponent (new RowComponent (pc.vibratorMsToBeOnLabel,      pc.vibratorMsToBeOnComboBox));
        paramsFourView .addRowComponent (new RowComponent (pc.localOnlyLabel,             pc.localOnlyButton));
        paramsFourView .addRowComponent (new RowComponent (pc.ongoingLabel,               pc.ongoingButton));
        paramsFourView .addRowComponent (new RowComponent (pc.timestampVisibilityLabel,   pc.timestampVisibilityComboBox));
        paramsFourView .addRowComponent (new RowComponent (pc.timeoutAfterLabel,          pc.timeoutAfterComboBox));
      #endif
    }

    struct ParamsView final : public Component
    {
        ParamsView()
        {
            // For now, to be able to dismiss mobile keyboard.
            setWantsKeyboardFocus (true);
        }

        void addRowComponent (RowComponent* rc)
        {
            rowComponents.add (rc);
            addAndMakeVisible (rc);
        }

        void resized() override
        {
            auto totalRowUnits = 0;

            for (auto* rc : rowComponents)
                totalRowUnits += rc->rowUnits;

            auto rowHeight = getHeight() / totalRowUnits;

            auto bounds = getLocalBounds();

            for (auto* rc : rowComponents)
                rc->setBounds (bounds.removeFromTop (rc->rowUnits * rowHeight));

            auto* last = rowComponents[rowComponents.size() - 1];
            last->setBounds (last->getBounds().withHeight (getHeight() - last->getY()));
        }

    private:
        OwnedArray<RowComponent> rowComponents;
    };

    struct AuxActionsView final : public Component
    {
        AuxActionsView()
        {
            addAndMakeVisible (getDeliveredNotificationsButton);
            addAndMakeVisible (removeDeliveredNotifWithIdButton);
            addAndMakeVisible (deliveredNotifIdentifier);
            addAndMakeVisible (removeAllDeliveredNotifsButton);
          #if JUCE_IOS || JUCE_MAC
            addAndMakeVisible (getPendingNotificationsButton);
            addAndMakeVisible (removePendingNotifWithIdButton);
            addAndMakeVisible (pendingNotifIdentifier);
            addAndMakeVisible (removeAllPendingNotifsButton);
          #endif

            // For now, to be able to dismiss mobile keyboard.
            setWantsKeyboardFocus (true);
        }

        void resized() override
        {
            auto columnWidth = getWidth();
            auto rowHeight = getHeight() / 6;
            auto bounds = getLocalBounds();

            getDeliveredNotificationsButton .setBounds (bounds.removeFromTop (rowHeight));

            auto rowBounds = bounds.removeFromTop (rowHeight);
            removeDeliveredNotifWithIdButton.setBounds (rowBounds.removeFromLeft (columnWidth / 2));
            deliveredNotifIdentifier        .setBounds (rowBounds);

            removeAllDeliveredNotifsButton  .setBounds (bounds.removeFromTop (rowHeight));

           #if JUCE_IOS || JUCE_MAC
            getPendingNotificationsButton .setBounds (bounds.removeFromTop (rowHeight));

            rowBounds = bounds.removeFromTop (rowHeight);
            removePendingNotifWithIdButton.setBounds (rowBounds.removeFromLeft (columnWidth / 2));
            pendingNotifIdentifier        .setBounds (rowBounds);

            removeAllPendingNotifsButton  .setBounds (bounds.removeFromTop (rowHeight));
           #endif
        }

        TextButton getDeliveredNotificationsButton  { "Get Delivered Notifications" };
        TextButton removeDeliveredNotifWithIdButton { "Remove Delivered Notif With ID:" };
        TextEditor deliveredNotifIdentifier;
        TextButton removeAllDeliveredNotifsButton   { "Remove All Delivered Notifs" };
        TextButton getPendingNotificationsButton    { "Get Pending Notifications" };
        TextButton removePendingNotifWithIdButton   { "Remove Pending Notif With ID:" };
        TextEditor pendingNotifIdentifier;
        TextButton removeAllPendingNotifsButton     { "Remove All Pending Notifs" };
    };

    struct RemoteView final : public Component
    {
        RemoteView()
        {
            addAndMakeVisible (getDeviceTokenButton);
           #if JUCE_ANDROID
            addAndMakeVisible (sendRemoteMessageButton);
            addAndMakeVisible (subscribeToSportsButton);
            addAndMakeVisible (unsubscribeFromSportsButton);
           #endif
        }

        void resized()
        {
            auto rowSize = getHeight() / 10;

            auto bounds = getLocalBounds().reduced (getWidth() / 10, getHeight() / 10);

            bounds.removeFromTop (2 * rowSize);

            getDeviceTokenButton       .setBounds (bounds.removeFromTop (rowSize));
            sendRemoteMessageButton    .setBounds (bounds.removeFromTop (rowSize));
            subscribeToSportsButton    .setBounds (bounds.removeFromTop (rowSize));
            unsubscribeFromSportsButton.setBounds (bounds.removeFromTop (rowSize));
        }

        TextButton getDeviceTokenButton        { "GetDeviceToken" };
        TextButton sendRemoteMessageButton     { "SendRemoteMessage" };
        TextButton subscribeToSportsButton     { "SubscribeToSports" };
        TextButton unsubscribeFromSportsButton { "UnsubscribeFromSports" };
    };

    struct DemoTabbedComponent final : public TabbedComponent
    {
        DemoTabbedComponent (PushNotificationsDemo& demoIn, TabbedButtonBar::Orientation orientation)
            : TabbedComponent (orientation), demo (demoIn)
        {
        }

        void currentTabChanged (int, const String& newCurrentTabName) override
        {
            if (! showedRemoteInstructions && newCurrentTabName == "Remote")
            {
                demo.showRemoteInstructions();
                showedRemoteInstructions = true;
            }
        }

    private:
        bool showedRemoteInstructions = false;
        PushNotificationsDemo& demo;
    };

    void showRemoteInstructions()
    {
       #if JUCE_IOS || JUCE_MAC
        auto options = MessageBoxOptions()
                                       .withIconType (MessageBoxIconType::InfoIcon)
                                       .withTitle ("Remote Notifications instructions")
                                       .withMessage ("In order to be able to test remote notifications "
                                                     "ensure that the app is signed and that you register "
                                                     "the bundle ID for remote notifications in "
                                                     "Apple Developer Center.")
                                       .withButton ("OK");
        messageBox = NativeMessageBox::showScopedAsync (options, nullptr);
       #endif
    }

    Label headerLabel { "headerLabel", "Push Notifications Demo" };
    ParamControls paramControls;
    ParamsView paramsOneView, paramsTwoView, paramsThreeView, paramsFourView;
    AuxActionsView auxActionsView;
    TabbedComponent localNotificationsTabs { TabbedButtonBar::TabsAtTop };
    RemoteView remoteView;
    DemoTabbedComponent mainTabs { *this, TabbedButtonBar::TabsAtTop };
    TextButton sendButton      { "Send!" };
    Label notAvailableYetLabel { "notAvailableYetLabel",
                                 "Push Notifications feature is not available on this platform yet!" };
    ScopedMessageBox messageBox;

    //==============================================================================
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PushNotificationsDemo)
};
