|  | /*
  ==============================================================================
   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.
  ==============================================================================
*/
#include "../JuceLibraryCode/JuceHeader.h"
#include "VoicePurchases.h"
//==============================================================================
class InAppPurchaseApplication  : public JUCEApplication,
                                  private AsyncUpdater
{
public:
    //==============================================================================
    InAppPurchaseApplication()   : voicePurchases (*this) {}
    //==============================================================================
    const String getApplicationName() override       { return ProjectInfo::projectName; }
    const String getApplicationVersion() override    { return ProjectInfo::versionString; }
    bool moreThanOneInstanceAllowed() override       { return false; }
    //==============================================================================
    void initialise (const String&) override
    {
        Desktop::getInstance().getDefaultLookAndFeel().setUsingNativeAlertWindows (true);
        dm.addAudioCallback (&player);
        dm.initialiseWithDefaultDevices (0, 2);
        mainWindow = new MainWindow;
    }
    void shutdown() override
    {
        mainWindow = nullptr;
        dm.closeAudioDevice();
        dm.removeAudioCallback (&player);
    }
    static InAppPurchaseApplication* getInstance()
    {
        return static_cast<InAppPurchaseApplication*> (JUCEApplication::getInstance());
    }
    //==============================================================================
    SoundPlayer& getPlayer()
    {
        return player;
    }
    VoicePurchases& getPurchases()
    {
        return voicePurchases;
    }
    //==============================================================================
    class MainContentComponent : public Component, private Button::Listener
    {
    public:
        MainContentComponent()
        {
            setOpaque (true);
            phraseListBox.setRowHeight (33);
            phraseListBox.selectRow (0);
            phraseListBox.updateContent();
            voiceListBox.setRowHeight (66);
            voiceListBox.selectRow (0);
            voiceListBox.updateContent();
            voiceListBox.getViewport()->setScrollOnDragEnabled (true);
            addAndMakeVisible (phraseLabel);
            addAndMakeVisible (phraseListBox);
            addAndMakeVisible (playStopButton);
            addAndMakeVisible (voiceLabel);
            addAndMakeVisible (voiceListBox);
            playStopButton.addListener (this);
           #if JUCE_ANDROID || JUCE_IOS
            auto screenBounds = Desktop::getInstance().getDisplays().getMainDisplay().userArea;
            setSize (screenBounds.getWidth(), screenBounds.getHeight());
           #else
            setSize (800, 600);
           #endif
        }
        void updateDisplay()
        {
            voiceListBox.updateContent();
            voiceListBox.repaint();
        }
    private:
        //==============================================================================
        class PhraseModel : public ListBoxModel
        {
        public:
            PhraseModel() {}
            int getNumRows() override    { return phrases.size(); }
            void paintListBoxItem (int row, Graphics& g, int w, int h, bool isSelected) override
            {
                Rectangle<int> r (0, 0, w, h);
                auto& lf = Desktop::getInstance().getDefaultLookAndFeel();
                g.setColour (lf.findColour (isSelected ? TextEditor::highlightColourId : ListBox::backgroundColourId));
                g.fillRect (r);
                g.setColour (lf.findColour (ListBox::textColourId));
                g.setFont (18);
                String phrase = (isPositiveAndBelow (row, phrases.size()) ? phrases[row] : String{});
                g.drawText (phrase, 10, 0, w, h, Justification::centredLeft);
            }
        private:
            static StringArray phrases;
            JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PhraseModel)
        };
        //==============================================================================
        class VoiceModel : public ListBoxModel
        {
        public:
            //==============================================================================
            class VoiceRow : public Component, private Button::Listener, private Timer
            {
            public:
                VoiceRow ()    : voices (getInstance()->getPurchases().getVoiceNames())
                {
                    addAndMakeVisible (nameLabel);
                    addAndMakeVisible (purchaseButton);
                    addAndMakeVisible (priceLabel);
                    purchaseButton.addListener (this);
                    setSize (600, 33);
                }
                void paint (Graphics& g) override
                {
                    auto r = getLocalBounds().reduced (4);
                    {
                        auto voiceIconBounds = r.removeFromLeft (r.getHeight());
                        g.setColour (Colours::black);
                        g.drawRect (voiceIconBounds);
                        voiceIconBounds.reduce (1, 1);
                        g.setColour (hasBeenPurchased ? Colours::white : Colours::grey);
                        g.fillRect (voiceIconBounds);
                        g.drawImage (avatar, voiceIconBounds.toFloat());
                        if (! hasBeenPurchased)
                        {
                            g.setColour (Colours::white.withAlpha (0.8f));
                            g.fillRect (voiceIconBounds);
                            if (purchaseInProgress)
                                getLookAndFeel().drawSpinningWaitAnimation (g, Colours::darkgrey,
                                                                            voiceIconBounds.getX(), voiceIconBounds.getY(),
                                                                            voiceIconBounds.getWidth(), voiceIconBounds.getHeight());
                        }
                    }
                }
                void resized() override
                {
                    auto r = getLocalBounds().reduced (4 + 8, 4);
                    auto h = r.getHeight();
                    auto w = static_cast<int> (h * 1.5);
                    r.removeFromLeft (h);
                    purchaseButton.setBounds (r.removeFromRight (w).withSizeKeepingCentre (w, h / 2));
                    nameLabel.setBounds (r.removeFromTop (18));
                    priceLabel.setBounds (r.removeFromTop (18));
                }
                void update (int rowNumber, bool rowIsSelected)
                {
                    isSelected  = rowIsSelected;
                    rowSelected = rowNumber;
                    if (isPositiveAndBelow (rowNumber, voices.size()))
                    {
                        auto imageResourceName = voices[rowNumber] + "_png";
                        nameLabel.setText (voices[rowNumber], NotificationType::dontSendNotification);
                        auto purchase = getInstance()->getPurchases().getPurchase (rowNumber);
                        hasBeenPurchased = purchase.isPurchased;
                        purchaseInProgress = purchase.purchaseInProgress;
                        if (purchaseInProgress)
                            startTimer (1000 / 50);
                        else
                            stopTimer();
                        nameLabel.setFont (Font (16).withStyle (Font::bold | (hasBeenPurchased ? 0 : Font::italic)));
                        nameLabel.setColour (Label::textColourId, hasBeenPurchased ? Colours::white : Colours::grey);
                        priceLabel.setFont (Font (10).withStyle (purchase.priceIsKnown ? 0 : Font::italic));
                        priceLabel.setColour (Label::textColourId, hasBeenPurchased ? Colours::white : Colours::grey);
                        priceLabel.setText (purchase.purchasePrice, NotificationType::dontSendNotification);
                        if (rowNumber == 0)
                        {
                            purchaseButton.setButtonText ("Internal");
                            purchaseButton.setEnabled (false);
                        }
                        else
                        {
                            purchaseButton.setButtonText (hasBeenPurchased ? "Purchased" : "Purchase");
                            purchaseButton.setEnabled (! hasBeenPurchased && purchase.priceIsKnown);
                        }
                        setInterceptsMouseClicks (! hasBeenPurchased, ! hasBeenPurchased);
                        int rawSize;
                        if (auto* rawData = BinaryData::getNamedResource (imageResourceName.toRawUTF8(), rawSize))
                        {
                            MemoryInputStream imageData (rawData, static_cast<size_t> (rawSize), false);
                            avatar = PNGImageFormat().decodeImage (imageData);
                        }
                    }
                }
            private:
                //==============================================================================
                void buttonClicked (Button*) override
                {
                    if (rowSelected >= 0)
                    {
                        if (! hasBeenPurchased)
                        {
                            getInstance()->getPurchases().purchaseVoice (rowSelected);
                            purchaseInProgress = true;
                            startTimer (1000 / 50);
                        }
                    }
                }
                void timerCallback() override   { repaint(); }
                //==============================================================================
                bool isSelected = false, hasBeenPurchased = false, purchaseInProgress = false;
                int rowSelected = -1;
                Image avatar;
                StringArray voices;
                Label nameLabel, priceLabel;
                TextButton purchaseButton {"Purchase"};
                JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VoiceRow)
            };
            //==============================================================================
            VoiceModel() : voiceProducts (getInstance()->getPurchases().getVoiceNames()) {}
            int getNumRows() override    { return voiceProducts.size(); }
            Component* refreshComponentForRow (int row, bool selected, Component* existing) override
            {
                if (isPositiveAndBelow (row, voiceProducts.size()))
                {
                    if (existing == nullptr)
                        existing = new VoiceRow;
                    if (auto* voiceRow = dynamic_cast<VoiceRow*> (existing))
                        voiceRow->update (row, selected);
                    return existing;
                }
                return nullptr;
            }
            void paintListBoxItem (int, Graphics& g, int w, int h, bool isSelected) override
            {
                auto r = Rectangle<int> (0, 0, w, h).reduced (4);
                auto& lf = Desktop::getInstance().getDefaultLookAndFeel();
                g.setColour (lf.findColour (isSelected ? TextEditor::highlightColourId : ListBox::backgroundColourId));
                g.fillRect (r);
            }
        private:
            StringArray voiceProducts;
            JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VoiceModel)
        };
        //==============================================================================
        void resized() override
        {
            auto r = getLocalBounds().reduced (20);
            {
                auto phraseArea = r.removeFromTop (r.getHeight() / 2);
                phraseLabel.setBounds (phraseArea.removeFromTop (36).reduced (0, 10));
                playStopButton.setBounds (phraseArea.removeFromBottom (50).reduced (0, 10));
                phraseListBox.setBounds (phraseArea);
            }
            {
                auto voiceArea = r;
                voiceLabel.setBounds (voiceArea.removeFromTop (36).reduced (0, 10));
                voiceListBox.setBounds (voiceArea);
            }
        }
        void paint (Graphics& g) override
        {
            g.fillAll (Desktop::getInstance().getDefaultLookAndFeel()
                          .findColour (ResizableWindow::backgroundColourId));
        }
        //==============================================================================
        void buttonClicked (Button*) override
        {
            MemoryOutputStream resourceName;
            auto idx = voiceListBox.getSelectedRow();
            if (isPositiveAndBelow (idx, soundNames.size()))
            {
                resourceName << soundNames[idx] << phraseListBox.getSelectedRow() << "_ogg";
                int numBytes;
                if (auto* data = BinaryData::getNamedResource (resourceName.toString().toRawUTF8(), numBytes))
                    getInstance()->getPlayer().play (data, static_cast<size_t> (numBytes));
            }
        }
        //==============================================================================
        StringArray soundNames = getInstance()->getPurchases().getVoiceNames();
        PhraseModel phraseModel;
        Label phraseLabel {"phraseLabel", NEEDS_TRANS ("Phrases:")};
        ListBox phraseListBox { "phraseListBox", &phraseModel };
        TextButton playStopButton {"Play"};
        VoiceModel voiceModel;
        Label voiceLabel {"voiceLabel", NEEDS_TRANS ("Voices:")};
        ListBox voiceListBox { "voiceListBox", &voiceModel };
        JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
    };
    //==============================================================================
    class MainWindow    : public DocumentWindow
    {
    public:
        MainWindow ()  : DocumentWindow (ProjectInfo::projectName,
                                         Desktop::getInstance().getDefaultLookAndFeel()
                                                               .findColour (ResizableWindow::backgroundColourId),
                                         DocumentWindow::allButtons)
        {
            setUsingNativeTitleBar (true);
            setContentOwned (new MainContentComponent(), true);
           #if JUCE_ANDROID || JUCE_IOS
            setFullScreen (true);
           #else
            centreWithSize (getWidth(), getHeight());
           #endif
            setVisible (true);
        }
        void closeButtonPressed() override
        {
            JUCEApplication::getInstance()->systemRequestedQuit();
        }
    private:
        JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainWindow)
    };
private:
    //==============================================================================
    void handleAsyncUpdate() override
    {
        if (auto* contentComponent = dynamic_cast<MainContentComponent*> (mainWindow->getContentComponent()))
            contentComponent->updateDisplay();
    }
    //==============================================================================
    VoicePurchases voicePurchases;
    AudioDeviceManager dm;
    SoundPlayer player;
    ScopedPointer<MainWindow> mainWindow;
    ScopedPointer<AlertWindow> alertWindow;
};
StringArray InAppPurchaseApplication::MainContentComponent::PhraseModel::phrases {{"I love JUCE!", "The five dimensions of touch", "Make it fast!"}};
//==============================================================================
// This macro generates the main() routine that launches the app.
START_JUCE_APPLICATION (InAppPurchaseApplication)
 |