mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-02-07 04:10:08 +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)
|
||||
194
examples/InAppPurchase/Source/VoicePurchases.h
Normal file
194
examples/InAppPurchase/Source/VoicePurchases.h
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
//==============================================================================
|
||||
class VoicePurchases : private InAppPurchases::Listener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
struct VoiceProduct
|
||||
{
|
||||
const char* identifier;
|
||||
const char* humanReadable;
|
||||
bool isPurchased, priceIsKnown, purchaseInProgress;
|
||||
String purchasePrice;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
VoicePurchases (AsyncUpdater& asyncUpdater)
|
||||
: guiUpdater (asyncUpdater)
|
||||
{
|
||||
voiceProducts = Array<VoiceProduct>(
|
||||
{VoiceProduct {"robot", "Robot", true, true, false, "Free" },
|
||||
VoiceProduct {"jules", "Jules", false, false, false, "Retrieving price..." },
|
||||
VoiceProduct {"fabian", "Fabian", false, false, false, "Retrieving price..." },
|
||||
VoiceProduct {"ed", "Ed", false, false, false, "Retrieving price..." },
|
||||
VoiceProduct {"lukasz", "Lukasz", false, false, false, "Retrieving price..." },
|
||||
VoiceProduct {"jb", "JB", false, false, false, "Retrieving price..." } });
|
||||
}
|
||||
|
||||
~VoicePurchases()
|
||||
{
|
||||
inAppPurchases.removeListener (this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
VoiceProduct getPurchase (int voiceIndex)
|
||||
{
|
||||
if (! havePurchasesBeenRestored)
|
||||
{
|
||||
havePurchasesBeenRestored = true;
|
||||
inAppPurchases.addListener (this);
|
||||
|
||||
inAppPurchases.restoreProductsBoughtList (true);
|
||||
}
|
||||
|
||||
return voiceProducts[voiceIndex];
|
||||
}
|
||||
|
||||
void purchaseVoice (int voiceIndex)
|
||||
{
|
||||
if (havePricesBeenFetched && isPositiveAndBelow (voiceIndex, voiceProducts.size()))
|
||||
{
|
||||
auto& product = voiceProducts.getReference (voiceIndex);
|
||||
|
||||
if (! product.isPurchased)
|
||||
{
|
||||
product.purchaseInProgress = true;
|
||||
inAppPurchases.purchaseProduct (product.identifier, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StringArray getVoiceNames() const
|
||||
{
|
||||
StringArray names;
|
||||
|
||||
for (auto& voiceProduct : voiceProducts)
|
||||
names.add (voiceProduct.humanReadable);
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
void productsInfoReturned (const Array<InAppPurchases::Product>& products) override
|
||||
{
|
||||
if (products.size() != 0)
|
||||
{
|
||||
for (auto product : products)
|
||||
{
|
||||
auto idx = findVoiceIndexFromIdentifier (product.identifier);
|
||||
|
||||
if (isPositiveAndBelow (idx, voiceProducts.size()))
|
||||
{
|
||||
auto& voiceProduct = voiceProducts.getReference (idx);
|
||||
|
||||
voiceProduct.priceIsKnown = true;
|
||||
voiceProduct.purchasePrice = product.price;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (! inAppPurchases.isInAppPurchasesSupported())
|
||||
{
|
||||
for (auto idx = 1; idx < voiceProducts.size(); ++idx)
|
||||
{
|
||||
auto& voiceProduct = voiceProducts.getReference (idx);
|
||||
|
||||
voiceProduct.isPurchased = false;
|
||||
voiceProduct.priceIsKnown = false;
|
||||
voiceProduct.purchasePrice = "In-App purcahses unavailable";
|
||||
}
|
||||
}
|
||||
|
||||
guiUpdater.triggerAsyncUpdate();
|
||||
}
|
||||
|
||||
void productPurchaseFinished (const PurchaseInfo& info, bool success, const String&) override
|
||||
{
|
||||
auto idx = findVoiceIndexFromIdentifier (info.purchase.productId);
|
||||
|
||||
if (isPositiveAndBelow (idx, voiceProducts.size()))
|
||||
{
|
||||
auto& voiceProduct = voiceProducts.getReference (idx);
|
||||
|
||||
voiceProduct.isPurchased = success;
|
||||
voiceProduct.purchaseInProgress = false;
|
||||
guiUpdater.triggerAsyncUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
void purchasesListRestored (const Array<PurchaseInfo>& infos, bool success, const String&) override
|
||||
{
|
||||
if (success)
|
||||
{
|
||||
for (auto& info : infos)
|
||||
{
|
||||
auto idx = findVoiceIndexFromIdentifier (info.purchase.productId);
|
||||
|
||||
if (isPositiveAndBelow (idx, voiceProducts.size()))
|
||||
{
|
||||
auto& voiceProduct = voiceProducts.getReference (idx);
|
||||
|
||||
voiceProduct.isPurchased = true;
|
||||
}
|
||||
}
|
||||
|
||||
guiUpdater.triggerAsyncUpdate();
|
||||
}
|
||||
|
||||
if (! havePricesBeenFetched)
|
||||
{
|
||||
havePricesBeenFetched = true;
|
||||
StringArray identifiers;
|
||||
|
||||
for (auto& voiceProduct : voiceProducts)
|
||||
identifiers.add (voiceProduct.identifier);
|
||||
|
||||
inAppPurchases.getProductsInformation(identifiers);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
int findVoiceIndexFromIdentifier (String identifier) const
|
||||
{
|
||||
identifier = identifier.toLowerCase();
|
||||
|
||||
for (auto i = 0; i < voiceProducts.size(); ++i)
|
||||
if (String (voiceProducts.getReference (i).identifier) == identifier)
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
AsyncUpdater& guiUpdater;
|
||||
bool havePurchasesBeenRestored = false, havePricesBeenFetched = false;
|
||||
InAppPurchases inAppPurchases;
|
||||
Array<VoiceProduct> voiceProducts;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VoicePurchases)
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue