1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00
JUCE/examples/InAppPurchase/Source/Main.cpp
2017-11-28 17:46:15 +00:00

428 lines
17 KiB
C++

/*
==============================================================================
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)