mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
428 lines
17 KiB
C++
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)
|