mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-14 00:14:18 +00:00
Added In-App Purchase sample code app
This commit is contained in:
parent
3d379863f1
commit
a092c19bcc
112 changed files with 18364 additions and 0 deletions
435
examples/InAppPurchase/Source/Main.cpp
Normal file
435
examples/InAppPurchase/Source/Main.cpp
Normal file
|
|
@ -0,0 +1,435 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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;
|
||||
|
||||
Timer::callAfterDelay(1000, [] ()
|
||||
{
|
||||
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
|
||||
"Your credit card will be charged!",
|
||||
"You are running the sample code for JUCE In-App purchases."
|
||||
"Although this is only sample code, it will still CHARGE YOUR CREDIT CARD!",
|
||||
"Understood!");
|
||||
});
|
||||
}
|
||||
|
||||
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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue