summaryrefslogtreecommitdiffstats
path: root/examples/demos
diff options
context:
space:
mode:
authorJani Korteniemi <jani.korteniemi@qt.io>2021-07-26 13:42:14 +0300
committerJani Korteniemi <jani.korteniemi@qt.io>2021-11-10 13:46:03 +0200
commit2eff5e431435f2b5be3511823400080d4029a6c1 (patch)
tree7d4647e00bdb463bc4bb29b85b445745bc18c0f3 /examples/demos
parent28dd0b133435e044224c3141265e14f2a2586efc (diff)
Add QtPurchasing module as an example under Qt demos
QtPurchasing module is deprecated in Qt6. However, the purchasing code is moved as example/demo under qtdoc to demonstrate the use of in-app purchases in Android/iOS. The demo is under demos\hangman. Few fixes done to the original code: * Ported the code to Qt 6. * Removed reinterpret_casts and QQmlListPropertys. * Fixed few QML warnings. * Added documentation * Fixed documentation * Added CMake port * Fixed Cmake for iOS * Modified Fonts in qml file * project name changed from qthangmanpurchasing to hangman Task-number: QTBUG-84776 Pick-to: 6.2 Change-Id: I86051b29d54cfb4a48b310ebc8d538c806fbf8da Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Nicholas Bennett <nicholas.bennett@qt.io> Reviewed-by: Assam Boudjelthia <assam.boudjelthia@qt.io>
Diffstat (limited to 'examples/demos')
-rw-r--r--examples/demos/CMakeLists.txt4
-rw-r--r--examples/demos/demos.pro2
-rw-r--r--examples/demos/hangman/CMakeLists.txt114
-rw-r--r--examples/demos/hangman/dict.txt130
-rw-r--r--examples/demos/hangman/doc/images/qthangman-example.pngbin0 -> 41985 bytes
-rw-r--r--examples/demos/hangman/doc/images/qthangman-store-example.pngbin0 -> 37374 bytes
-rw-r--r--examples/demos/hangman/doc/src/androidclasses.qdoc100
-rw-r--r--examples/demos/hangman/doc/src/appstore.qdoc189
-rw-r--r--examples/demos/hangman/doc/src/baseclass.qdoc160
-rw-r--r--examples/demos/hangman/doc/src/gettingstarted-qml.qdoc275
-rw-r--r--examples/demos/hangman/doc/src/googleplay.qdoc112
-rw-r--r--examples/demos/hangman/doc/src/iosclasses.qdoc92
-rw-r--r--examples/demos/hangman/doc/src/qtpurchasing-overview.qdoc129
-rw-r--r--examples/demos/hangman/hangman.pro21
-rw-r--r--examples/demos/hangman/hangmangame.cpp278
-rw-r--r--examples/demos/hangman/hangmangame.h140
-rw-r--r--examples/demos/hangman/main.cpp67
-rw-r--r--examples/demos/hangman/main.qml92
-rw-r--r--examples/demos/hangman/purchasing/android/androidinappproduct.cpp71
-rw-r--r--examples/demos/hangman/purchasing/android/androidinappproduct.h76
-rw-r--r--examples/demos/hangman/purchasing/android/androidinapppurchasebackend.cpp332
-rw-r--r--examples/demos/hangman/purchasing/android/androidinapppurchasebackend.h145
-rw-r--r--examples/demos/hangman/purchasing/android/androidinapptransaction.cpp124
-rw-r--r--examples/demos/hangman/purchasing/android/androidinapptransaction.h90
-rw-r--r--examples/demos/hangman/purchasing/android/androidjni.cpp153
-rw-r--r--examples/demos/hangman/purchasing/android/build.gradle79
-rw-r--r--examples/demos/hangman/purchasing/android/src/org/qtproject/qt/android/purchasing/Base64.java572
-rw-r--r--examples/demos/hangman/purchasing/android/src/org/qtproject/qt/android/purchasing/Base64DecoderException.java34
-rw-r--r--examples/demos/hangman/purchasing/android/src/org/qtproject/qt/android/purchasing/InAppPurchase.java375
-rw-r--r--examples/demos/hangman/purchasing/android/src/org/qtproject/qt/android/purchasing/Security.java131
-rw-r--r--examples/demos/hangman/purchasing/inapp/inappproduct.cpp107
-rw-r--r--examples/demos/hangman/purchasing/inapp/inappproduct.h96
-rw-r--r--examples/demos/hangman/purchasing/inapp/inapppurchasebackend.cpp92
-rw-r--r--examples/demos/hangman/purchasing/inapp/inapppurchasebackend.h102
-rw-r--r--examples/demos/hangman/purchasing/inapp/inappstore.cpp155
-rw-r--r--examples/demos/hangman/purchasing/inapp/inappstore.h114
-rw-r--r--examples/demos/hangman/purchasing/inapp/inapptransaction.cpp113
-rw-r--r--examples/demos/hangman/purchasing/inapp/inapptransaction.h117
-rw-r--r--examples/demos/hangman/purchasing/ios/iosinapppurchasebackend.h98
-rw-r--r--examples/demos/hangman/purchasing/ios/iosinapppurchasebackend.mm299
-rw-r--r--examples/demos/hangman/purchasing/ios/iosinapppurchaseproduct.h81
-rw-r--r--examples/demos/hangman/purchasing/ios/iosinapppurchaseproduct.mm89
-rw-r--r--examples/demos/hangman/purchasing/ios/iosinapppurchasetransaction.h92
-rw-r--r--examples/demos/hangman/purchasing/ios/iosinapppurchasetransaction.mm135
-rw-r--r--examples/demos/hangman/purchasing/purchasing.pri66
-rw-r--r--examples/demos/hangman/purchasing/qmltypes/inappproductqmltype.cpp260
-rw-r--r--examples/demos/hangman/purchasing/qmltypes/inappproductqmltype.h146
-rw-r--r--examples/demos/hangman/purchasing/qmltypes/inappstoreqmltype.cpp68
-rw-r--r--examples/demos/hangman/purchasing/qmltypes/inappstoreqmltype.h83
-rw-r--r--examples/demos/hangman/qml/GameView.qml232
-rw-r--r--examples/demos/hangman/qml/GuessWordView.qml105
-rw-r--r--examples/demos/hangman/qml/Hangman.qml270
-rw-r--r--examples/demos/hangman/qml/HowToView.qml153
-rw-r--r--examples/demos/hangman/qml/Key.qml66
-rw-r--r--examples/demos/hangman/qml/Letter.qml83
-rw-r--r--examples/demos/hangman/qml/LetterSelector.qml425
-rw-r--r--examples/demos/hangman/qml/MainView.qml93
-rw-r--r--examples/demos/hangman/qml/PageHeader.qml93
-rw-r--r--examples/demos/hangman/qml/ScoreItem.qml75
-rw-r--r--examples/demos/hangman/qml/SimpleButton.qml129
-rw-r--r--examples/demos/hangman/qml/SplashScreen.qml87
-rw-r--r--examples/demos/hangman/qml/StoreItem.qml194
-rw-r--r--examples/demos/hangman/qml/StoreView.qml150
-rw-r--r--examples/demos/hangman/qml/Word.qml74
-rw-r--r--examples/demos/hangman/resources.qrc21
65 files changed, 8650 insertions, 0 deletions
diff --git a/examples/demos/CMakeLists.txt b/examples/demos/CMakeLists.txt
index abbf03617..1893137d8 100644
--- a/examples/demos/CMakeLists.txt
+++ b/examples/demos/CMakeLists.txt
@@ -11,8 +11,12 @@ if(TARGET Qt::Quick)
endif()
if(TARGET Qt::Quick AND TARGET Qt::QuickControls2)
add_subdirectory(coffee)
+ if(ANDROID OR IOS)
+ add_subdirectory(hangman)
+ endif()
endif()
if(TARGET Qt::Quick AND TARGET Qt::Network AND TARGET Qt::QmlXmlListModel)
add_subdirectory(rssnews)
add_subdirectory(photoviewer)
endif()
+
diff --git a/examples/demos/demos.pro b/examples/demos/demos.pro
index f81c62a77..9a1487c21 100644
--- a/examples/demos/demos.pro
+++ b/examples/demos/demos.pro
@@ -12,6 +12,8 @@ qtHaveModule(quick) {
qtHaveModule(quickcontrols2) {
SUBDIRS += coffee
+
+ android|ios: SUBDIRS += hangman
}
qtHaveModule(network) {
diff --git a/examples/demos/hangman/CMakeLists.txt b/examples/demos/hangman/CMakeLists.txt
new file mode 100644
index 000000000..629b34f7b
--- /dev/null
+++ b/examples/demos/hangman/CMakeLists.txt
@@ -0,0 +1,114 @@
+# Generated from hangman.pro.
+
+#####################################################################
+## hangman Binary:
+#####################################################################
+
+cmake_minimum_required(VERSION 3.16)
+project(hangman LANGUAGES CXX)
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+set(CMAKE_AUTOMOC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/demos/hangman")
+set(OUTPUT_DIRECTORY "${INSTALL_EXAMPLESDIR}/demos/hangman")
+
+find_package(Qt6 COMPONENTS Core)
+find_package(Qt6 COMPONENTS Gui)
+find_package(Qt6 COMPONENTS Qml)
+find_package(Qt6 COMPONENTS Quick)
+find_package(Qt6 COMPONENTS QuickControls2)
+
+qt_add_executable(hangman
+ MANUAL_FINALIZATION
+ hangmangame.cpp hangmangame.h
+ main.cpp
+ purchasing/inapp/inappproduct.cpp purchasing/inapp/inappproduct.h
+ purchasing/inapp/inapppurchasebackend.cpp purchasing/inapp/inapppurchasebackend.h
+ purchasing/inapp/inappstore.cpp purchasing/inapp/inappstore.h
+ purchasing/inapp/inapptransaction.cpp purchasing/inapp/inapptransaction.h
+ purchasing/qmltypes/inappproductqmltype.cpp purchasing/qmltypes/inappproductqmltype.h
+ purchasing/qmltypes/inappstoreqmltype.cpp purchasing/qmltypes/inappstoreqmltype.h
+)
+
+target_include_directories(hangman PRIVATE
+ purchasing/inapp
+ purchasing/qmltypes
+)
+
+qt_add_qml_module(hangman
+ URI Hangman
+ VERSION 1.0
+ NO_RESOURCE_TARGET_PATH
+ QML_FILES
+ qml/GameView.qml
+ qml/GuessWordView.qml
+ qml/Hangman.qml
+ qml/HowToView.qml
+ qml/Key.qml
+ qml/Letter.qml
+ qml/LetterSelector.qml
+ qml/MainView.qml
+ qml/PageHeader.qml
+ qml/ScoreItem.qml
+ qml/SimpleButton.qml
+ qml/SplashScreen.qml
+ qml/StoreItem.qml
+ qml/StoreView.qml
+ qml/Word.qml
+ main.qml
+)
+
+qt_add_resources(hangman "resources"
+ PREFIX
+ "/"
+ FILES
+ dict.txt
+)
+
+if(ANDROID)
+ set_property(TARGET hangman APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
+ ${CMAKE_CURRENT_SOURCE_DIR}/purchasing/android/
+ )
+
+ target_sources(hangman PRIVATE
+ purchasing/android/androidinappproduct.cpp purchasing/android/androidinappproduct.h
+ purchasing/android/androidinapppurchasebackend.cpp purchasing/android/androidinapppurchasebackend.h
+ purchasing/android/androidinapptransaction.cpp purchasing/android/androidinapptransaction.h
+ purchasing/android/androidjni.cpp
+ )
+endif()
+
+if(IOS)
+ target_sources(hangman PRIVATE
+ purchasing/ios/iosinapppurchasebackend.h purchasing/ios/iosinapppurchasebackend.mm
+ purchasing/ios/iosinapppurchaseproduct.h purchasing/ios/iosinapppurchaseproduct.mm
+ purchasing/ios/iosinapppurchasetransaction.h purchasing/ios/iosinapppurchasetransaction.mm
+ )
+
+ target_link_libraries(hangman PRIVATE
+ "-framework Foundation"
+ "-framework StoreKit"
+ )
+endif()
+
+target_link_libraries(hangman PRIVATE
+ Qt::Core
+ Qt::Gui
+ Qt::Qml
+ Qt::Quick
+ Qt::QuickControls2
+)
+
+qt_finalize_executable(hangman)
+
+install(TARGETS hangman
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/examples/demos/hangman/dict.txt b/examples/demos/hangman/dict.txt
new file mode 100644
index 000000000..2f3993558
--- /dev/null
+++ b/examples/demos/hangman/dict.txt
@@ -0,0 +1,130 @@
+above
+accept
+alternate
+animator
+attach
+bean
+bookmark
+brand
+bug
+bytes
+cape
+central
+chipping
+coded
+crime
+cute
+deal
+demise
+divers
+drag
+duration
+easy
+elite
+employ
+energy
+estate
+fast
+flower
+forbidding
+friend
+fun
+gateway
+glance
+governor
+guest
+gyroscope
+hamburger
+hilarious
+hex
+holster
+hyper
+ice
+idiot
+impacted
+include
+italic
+jail
+jigsaw
+jolly
+judging
+justifications
+karma
+ketchup
+kilometers
+knight
+koala
+lake
+lecture
+lift
+loaded
+lucky
+lynx
+macro
+medium
+middlemen
+mixed
+monitor
+native
+necessary
+nobody
+nuclear
+numerically
+oak
+obviously
+optical
+organization
+overflow
+paddles
+peek
+player
+popular
+proxies
+python
+quad
+queen
+quick
+quotation
+qwerty
+radioactive
+redrawing
+rocket
+rows
+safety
+schoolbook
+season
+smoke
+specialist
+tailor
+theorizing
+tracked
+turmoil
+typography
+ubiquity
+uncanny
+update
+upward
+utilizing
+vanish
+verse
+village
+vote
+vulnerability
+wagon
+western
+worship
+wrong
+xenon
+xi
+xmas
+xylitol
+yahoo
+yard
+yellow
+yesterday
+younger
+zebra
+zero
+zip
+zombie
+zooming
diff --git a/examples/demos/hangman/doc/images/qthangman-example.png b/examples/demos/hangman/doc/images/qthangman-example.png
new file mode 100644
index 000000000..df54b8d4e
--- /dev/null
+++ b/examples/demos/hangman/doc/images/qthangman-example.png
Binary files differ
diff --git a/examples/demos/hangman/doc/images/qthangman-store-example.png b/examples/demos/hangman/doc/images/qthangman-store-example.png
new file mode 100644
index 000000000..99583b520
--- /dev/null
+++ b/examples/demos/hangman/doc/images/qthangman-store-example.png
Binary files differ
diff --git a/examples/demos/hangman/doc/src/androidclasses.qdoc b/examples/demos/hangman/doc/src/androidclasses.qdoc
new file mode 100644
index 000000000..8e4224438
--- /dev/null
+++ b/examples/demos/hangman/doc/src/androidclasses.qdoc
@@ -0,0 +1,100 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:FDL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Free Documentation License Usage
+** Alternatively, this file may be used under the terms of the GNU Free
+** Documentation License version 1.3 as published by the Free Software
+** Foundation and appearing in the file included in the packaging of
+** this file. Please review the following information to ensure
+** the GNU Free Documentation License version 1.3 requirements
+** will be met: https://www.gnu.org/licenses/fdl-1.3.html.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*!
+ \page qtpurchasing-androidclasses.html
+ \title Demo Android Classes
+ \brief Android classes are used in the purchasing demo to enable purchasing in Android.
+ \ingroup qtpurchasing-examples
+
+ \section1 Classes
+ \table
+ \row
+ \li \l AndroidInAppProduct
+ \li A product registered in the store.
+ \row
+ \li \l AndroidInAppTransaction
+ \li Contains information about a transaction in the external app store.
+ \row
+ \li \l AndroidInAppPurchaseBackend
+ \li Comminucates with external store.
+ \row
+ \li \l androidjni.cpp and InAppPurchase.java
+ \li Communication between Google Play's billing system and C++.
+ \endtable
+
+ Check out \l {Base Classes}
+
+ \section1 AndroidInAppProduct
+
+ AndroidInAppProduct adds purchase() for initializing purchasing process.
+
+ \section1 AndroidInAppTransaction
+
+ AndroidInAppTransaction is created in the AndroidInAppPurchaseBackend using
+ purchaseSucceeded(), purchaseFailed() and checkFinalizationStatus().
+
+ AndroidInAppTransaction adds new parameters and return types.
+ Class has finalize() function that separates Consumable and Unlockable
+ products to be either acknowledged or consumed in the InAppPurchase.java.
+
+ \section1 AndroidInAppPurchaseBackend
+
+ AndroidInAppPurchaseBackend makes and receives calls from InAppPurchase.java.
+ This class is one of the back end components in the application.
+
+ \section2 Initialize
+
+ Shows the products information on the store page.
+
+ \list 1
+ \li AndroidInAppPurchaseBackend is called from InAppStore::setupBackend().
+ \li At initialization AndroidInAppPurchaseBackend will give \l {https://developer.android.com/reference/android/content/Context} {Context} and
+ pointer of its self to the Java in the calls constructor and then
+ initializes the connection to the Google Play store using the initialize()
+ function.
+ \li When the connection is successful, androidjni will call registerRedy() which
+ emits the ready() singnal to InAppStore and starts queryProducts() function to get
+ the information of the purchasable items.
+ \li When the query is done, productQueryDone() signal is emitted and
+ the item's information will be visible on the application's store page.
+ \endlist
+
+ \section2 Purchasing
+
+ When pressing one of the products on the applications store page a call is routed
+ through \c AndroidInAppProduct::purchase() to purchaseProduct() function. This then calls
+ the Java method launchBillingFlow() which opens Goole Plays billing flow to the user.
+
+ \section1 InAppPurchase.java and androidjni.cpp
+
+ AndroidInAppPurchaseBackend makes calls directly to InAppPurchase.java and
+ receives Java calls from androidjni. The demo application communicates
+ with Java function using \l QJniObjects.
+
+ \sa \l {https://developer.android.com/google/play/billing/integrate#java}{Integrate the Google Play Billing Library}, \l QJniObjects and \l QJniEnvironment.
+*/
diff --git a/examples/demos/hangman/doc/src/appstore.qdoc b/examples/demos/hangman/doc/src/appstore.qdoc
new file mode 100644
index 000000000..86d466bb2
--- /dev/null
+++ b/examples/demos/hangman/doc/src/appstore.qdoc
@@ -0,0 +1,189 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:FDL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Free Documentation License Usage
+** Alternatively, this file may be used under the terms of the GNU Free
+** Documentation License version 1.3 as published by the Free Software
+** Foundation and appearing in the file included in the packaging of
+** this file. Please review the following information to ensure
+** the GNU Free Documentation License version 1.3 requirements
+** will be met: https://www.gnu.org/licenses/fdl-1.3.html.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*!
+
+ \page qtpurchasing-appstore.html
+ \title Registering Products in App Store
+ \brief A short guide to registering in-app products in the iOS App Store.
+
+ To take advantage of in-app purchasing functionality on iOS, register your
+ application and all the available products in
+ \l{https://appstoreconnect.apple.com/login}{App Store Connect}.
+ This guide provides a brief introduction on how to register an application
+ and it's in-app products on App Store.
+
+ In-App purchasing can only be tested on the actual hardware for the iOS
+ platform. For more information refer to the
+ \l{http://developer.apple.com/in-app-purchase/}{iOS documentation}.
+
+
+ \section1 Register a unique app ID
+
+ One of the requirements to support in-app purchases on iOS is to have
+ a registered unique App ID. This means that it is not possible to use
+ an App ID that contains a wildcard character. You can check the existing App
+ IDs and create new ones from the
+ \l{https://developer.apple.com/account/ios/identifiers/bundle/bundleList.action}{Apple Developer Certificate, Identifiers, and Profiles manager}.
+
+ In the \uicontrol{Identifiers} tab of the
+ \uicontrol{Developer Certificate, Identifiers, and Profiles} manager, click
+ the \uicontrol{+} button to create a New App ID. This will open up the page
+ to register a new iOS App ID.
+
+ Give your App a relevant ID description and prefix. Your App ID Suffix
+ should be an Explicit App ID type. Enter a unique App ID in the Bundle ID
+ field. It is recommended to use the com.mycompany.myappname convention for
+ App ID. Where, the "com.mycompany" is the Internet domain name of the
+ publisher.
+
+ In the \uicontrol{App Services} area make sure that the
+ \uicontrol{In-App Purchases} is checked. Click \uicontrol{continue} your to
+ complete the App ID registration.
+
+ \section1 Create a provisioning profile
+
+ To be able to use your new App ID, generate a new provision profile that
+ includes the new ID to sign and run your app. Provisioning Profiles are also
+ managed through the
+ \uicontrol{Apple Developer Certificate, Identifiers, and Profiles} manager.
+
+ Navigate to the \uicontrol{Provisioning Profiles} tab of the manager and
+ click the \uicontrol{+} button to create a new Provisioning Profile. Make
+ sure that the \uicontrol{iOS App Development} is selected and click the
+ \uicontrol{Continue} button.
+
+ You are now given an option to choose the App ID to use for this profile,
+ which will be the unique App ID you created in the previous section. Once
+ again make sure that the App ID does not contain any wildcard characters
+ (“*”) as it is not be possible to use the In-App Purchasing service with
+ such an App ID.
+
+ On the next page you will need to select the certificates that will be
+ distributed with your Provisioning Profile. You would have needed to uploaded
+ a certificate on your local development machine to the Apple Developer
+ Certificate, Identifiers, and Profiles manger previously. Generally you will
+ want to add any certificates of the members of your team who will need to build
+ your App.
+
+ On the next page you will need to select the devices you will be developing and
+ testing your app on. You must register your devices to the Apple Developer
+ Certificate, Identifiers, and Profiles manger previously. The devices you
+ select here are the only ones capable of running apps signed against this
+ provisioning profile, so select all the devices you intend use during the
+ development and testing phase.
+
+ Give your Provisioning Profile a descriptive display name, and click
+ \uicontrol{Generate} to create the actual profile. When this process is
+ completed you’ll be given a choice to download the Provisioning Profile.
+
+ Open the downloaded file in XCode to install. Now the next time you plug in
+ one of the registered devices, your Provisioning Profile should be installed
+ to it automatically.
+
+ \section1 Register your application
+
+ Before you can register new products available to the in-app purchasing service,
+ you need to register your application in App Store Connect. App Store Connect is the
+ place where you register an app when you want to submit it to the Apple iOS
+ App Store.
+
+ Start by going to \l{https://appstoreconnect.apple.com/login}{App Store Connect} and
+ logging in. Once logged in open the \uicontrol{My Apps} link that will take you
+ to \uicontrol{Apps} page. If you have already registered your app here, you can
+ skip the registration step, otherwise click the plus button and select
+ \uicontrol{New App} in the top left corner of the page.
+
+ Select iOS App and then proceed to fill out the required information on the
+ next page. When you get to the \uicontrol{Bundle ID} section select the
+ unique App ID we created in the previous steps.
+
+ It is likely at this point that you do not have all the necessary details to
+ complete the information required to register your app. For now, just fill
+ out the forms with stub data as everything except the \uicontrol{SKU} and
+ \uicontrol{Version} fields are editable later.
+
+ \section1 Archive and uploading application
+
+ When you build application for iOs device Qt Creator creates XCode project
+ that will be used for uploading your application to the App Store. Open the
+ XCode project and go to \c {signing and Capabilities} from your project
+ properties and make sure that the Apple ID is selected what you use on App
+ Store Connect. Make Sure that the \c {Bundle Identifier} is the same as that you
+ created on the \uicontrol{Developer Certificate, Identifiers, and Profiles} page.
+
+ Next select \c {Set the active scheme} from the top bar of the XCode window and
+ select \c {Generic iOS Device}. Afte that select \c Product on the menu bar and
+ click \c Archive. On the pop-up window select \c {Distribute App} go through the
+ wizard and the build will show up to the App Store Connect.
+
+ \section1 Setting up application testing
+
+ From the App Store Connect select your application and go to \TestFlight page.
+ You should see at least one build of your application in the page. there are two
+ type of testing groups internal and external. In internal testing group you can
+ select developer who has access to your application on the App Store Connect. In
+ external group you can share your application with public link or you can invite
+ specific people to test your application.
+
+ \section1 Register the products
+
+ Once your app is registered in App Store Connect you will have additional options
+ available to manage that App. In the \uicontrol{Apps} page of App Sotre
+ Connect, click the icon representing your app to manage its details. Within this
+ menu you can select the \uicontrol{Manage In-App Purchases} from list on the right
+ to register in-app products you wish to offer.
+
+ To register a new product, click the \uicontrol{Plus icon} button in the right
+ of the "In-App Purchase" header. The first choice you will be given is the type of
+ in-app purchase you would like to create.
+
+ On the next page, you must fill out the details about your product. The
+ \uicontrol{Reference Name} field refers to how the product will be
+ displayed in App Store Connect and in sales reports, but not in the
+ App Store itself. The \uicontrol{Product ID} field is very important
+ as it is the unique ID that will be used to query for the product from your
+ app itself. If possible make this the same ID as the corresponding products
+ in the other platform’s stores, otherwise you will have to conditional logic
+ in your app for each platform when specifying product identifiers.
+
+ Select the pricing and availability details for you product next. For the iOS
+ App Store, pricing is set via a tier system. Each tier represents a price
+ level in each iOS App Store region. Your end users will be given the actual
+ price expected for a product in terms of what region their App Store is in.
+
+ In the next section \uicontrol{In-App Purchase Details} you specify the details
+ for at least one language. When the app requests the product details like
+ \c title and \c description, they will be provided in the language of their
+ locale if they are available. Create language details for the regions you
+ expect to distribute app in.
+
+ Finally, you will need to attach a screenshot that will be used for review
+ purposes. This again can be a stub for now, but your product will need to
+ pass the review before it can go live to be sold in your published app. Click
+ \uicontrol{save} to complete the product registration.
+*/
diff --git a/examples/demos/hangman/doc/src/baseclass.qdoc b/examples/demos/hangman/doc/src/baseclass.qdoc
new file mode 100644
index 000000000..ef71f6b69
--- /dev/null
+++ b/examples/demos/hangman/doc/src/baseclass.qdoc
@@ -0,0 +1,160 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:FDL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Free Documentation License Usage
+** Alternatively, this file may be used under the terms of the GNU Free
+** Documentation License version 1.3 as published by the Free Software
+** Foundation and appearing in the file included in the packaging of
+** this file. Please review the following information to ensure
+** the GNU Free Documentation License version 1.3 requirements
+** will be met: https://www.gnu.org/licenses/fdl-1.3.html.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*!
+ \page qtpurchasing-baseclasses.html
+ \title Base Classes
+ \brief Base classes are used in purchasing demo to enable cross-platform for Android and iOS.
+ \ingroup qtpurchasing-examples
+
+ \section1 Classes
+ \table
+ \header
+ \li Class
+ \li Description
+ \row
+ \li \l InAppProduct
+ \li A product registered in the store.
+ \row
+ \li \l InAppStore
+ \li Main entry point for managing in-app purchases.
+ \row
+ \li \l InAppTransaction
+ \li Contains information about a transaction in the external app store.
+ \row
+ \li \l InAppPurchaseBackend
+ \li Communicates with the external store.
+ \endtable
+
+ \section1 InAppProduct
+
+ InAppProduct encapsulates a product in the external store after it has been registered in
+ InAppStore and confirmed to exist. It has an identifier which matches the identifier of the
+ product in the external store, it has a price which is retrieved from the external store,
+ and it has a product type.
+
+ The product type can be \c Consumable or \c Unlockable. Consumable products can be
+ purchased any number of times as long as each transaction is finalized explicitly by the
+ application. Unlockable types can only be purchased once.
+
+ InAppProduct has 5 returnable variables \c price, \c title, \c description \c identifier and
+ \c productType. All return a QString except productType.
+
+ ProductType is an enum type and returns \c 0 if it is Consumable and \c 1 if Unlockable.
+ \code
+ enum ProductType
+ {
+ Consumable,
+ Unlockable
+ };
+ \endcode
+
+ Check out the derived classes \l AndroidInAppProduct for Android and \l IosInAppProduct for iOS.
+
+ \section1 InAppStore
+
+ The main entry point for managing in-app purchases. It is the base class for AndroidInAppProduct and
+ IosInAppProduct.
+
+ InAppStore is used for managing in-app purchases in the application in a cross-platform way.
+ Depending on the compiler, InAppStore checks what platform is available using \c Macros.
+
+ \code
+ void InAppStore::setupBackend()
+ {
+ #ifdef Q_OS_ANDROID
+ d->backend = new AndroidInAppPurchaseBackend;
+ #endif
+
+ #ifdef Q_OS_IOS
+ d->backend = new IosInAppPurchaseBackend;
+ #endif
+
+ d->backend->setStore(this);
+
+ ...
+ \endcode
+
+ \section2 Initializing the store
+
+ Upon going to the store page in the demo, InAppStore connects all signals to related slots in
+ setupBackend() function. Then uses the registerProduct() function to register product ID and
+ productType of each product registered in the external store to a QHash registeredProducts.
+
+ Registering a product is asynchronous, and will at some point yield productRegistered()
+ signals when its found from external store.
+
+ \code
+ void InAppStore::registerProduct(InAppProduct *product)
+ {
+ d->registeredProducts[product->identifier()] = product;
+ emit productRegistered(product);
+ }
+ \endcode
+
+ \section2 Completing a purchase
+
+ Once the items have been successfully registered in the store, The user can purchase them by
+ pressing on of the products on the apps store page. QML will call product.purchase()
+ function in AndroidInAppProduct or IosInAppProduct which will open the external store's
+ purchasing flow.
+
+ When a purchase has been completed regardless of success, the transactionRedy signal will be
+ sent to \c InAppProductQmlType, to notify QML to start finalize the transaction.
+
+ section2 Restoring purchases
+
+ In the demo unlockable purchases will be saved on the apps storage. By clearing the storage
+ the user will lose the unlockable purchase and it cannot be purchased again, as according to
+ the external store it is already owned.
+
+ You can use the \c{restore purchases} button in the apps store page to restore your unlockable purchases.
+ The restore purchases button calls the restorePurchases() function and will check the external store for already owned
+ purchases. It emits the transactionRedy() signal to finalize and restore the purchase.
+
+ InAppStore has no derived classes.
+
+ \section1 InAppTransaction
+
+ InAppTransaction contains information about a transaction in the external app store and is
+ usually provided as a result of calling InAppProduct::purchase(). When the purchase flow has
+ been completed by the user (confirming the purchase, for instance by entering their password),
+ the InAppStore instance containing the product will emit a InAppStore::transactionReady()
+ signal with data about the transaction.
+
+ The status() function provides information on if the transaction was successful or not. If it was
+ successful, then the application should take appropriate action. When the necessary action has
+ been performed, finalize() should be called. The finalize() function should be called regardless
+ of the status of the transaction.
+
+ Check out the derived classes \l {AndroidInAppTransaction}{AndroidInAppTransaction} for android and \l {IosInAppTransaction}{IosInAppTransaction} for iOS.
+
+ \section1 InAppPurchaseBackend
+
+ InAppPurchaseBackend is used to create derived classs for
+ AndroidInAppPurchaseBackend and IosInAppPurchaseBackend
+*/
diff --git a/examples/demos/hangman/doc/src/gettingstarted-qml.qdoc b/examples/demos/hangman/doc/src/gettingstarted-qml.qdoc
new file mode 100644
index 000000000..63d6f1629
--- /dev/null
+++ b/examples/demos/hangman/doc/src/gettingstarted-qml.qdoc
@@ -0,0 +1,275 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:FDL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Free Documentation License Usage
+** Alternatively, this file may be used under the terms of the GNU Free
+** Documentation License version 1.3 as published by the Free Software
+** Foundation and appearing in the file included in the packaging of
+** this file. Please review the following information to ensure
+** the GNU Free Documentation License version 1.3 requirements
+** will be met: https://www.gnu.org/licenses/fdl-1.3.html.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*!
+
+ \page qtpurchasing-gettingstarted-qml.html
+ \title Getting Started with Qt Purchasing in QML
+ \brief Guide to getting started with Qt Purchasing using QML.
+
+ This guide assumes that you have registered the in-app products for your
+ application in the external store. For more information about registering
+ products, see \l{Registering Products in Google Play} and
+ \l{Registering Products in App Store}.
+
+ \section1 Preparing the application
+
+ Register and import your QML types. The QML types can be imported into your
+ application for example using the following import statement:
+
+ \qml \QtMinorVersion
+ import com.mycompany.myappname
+ \endqml
+
+ And by adding following statements in the \c .pro file to link against defined QML types:
+
+ \code
+ CONFIG += qmltypes
+ QML_IMPORT_NAME = com.mycompany.myappname
+ QML_IMPORT_MAJOR_VERSION = 1
+ \endcode
+
+ For more information check out \l{https://doc.qt.io/qt-5/qtqml-cppintegration-definetypes.html}{Defining QML Types from C++}.
+
+ \section1 Registering products
+
+ Before you can operate on the products in your code, they must be
+ registered in the QML graph. You start by making a \l{Store} item,
+ and then create each product as a child of this.
+
+ \qml
+ Store {
+ Product {
+ identifier: "consumableProduct"
+ type: Product.Consumable
+
+ // ...
+ }
+
+ Product {
+ identifier: "unlockableProduct"
+ type: Product.Unlockable
+
+ // ...
+ }
+ }
+ \endqml
+
+ As you can see, there are consumable products and unlockable products. The former
+ can be purchased any number of times by the same user, while the latter can only
+ be purchased once.
+
+ \section1 The product declaration
+
+ For each product you must fill out the \c identifier, before the product can
+ be queried from the external store. You should also always add a \l{QtPurchasing::Product::onPurchaseSucceeded}{onPurchaseSucceeded}
+ and a \l{QtPurchasing::Product::onPurchaseFailed}{onPurchaseFailed} handler if you intend to provide the option to purchase
+ the products. If you are also using the restore functionality, you should add a
+ \l{QtPurchasing::Product::onPurchaseRestored}{onPurchaseRestored} handler to your unlockable products.
+
+ The signal handlers should handle the incoming transaction. Once the transaction
+ has been handled appropriately, it should be finalized. For instance, when a purchase
+ has succeeded, it's appropriate to save information about the purchased product in
+ persistent storage, so that this product can still be available the next time the
+ application launches.
+
+ The following example calls custom methods to save data about a succeeded purchase so that
+ it survives across application runs. After verifying that the data has been stored, it finalizes
+ the transaction. When the transaction has failed, it displays information about the failure
+ to the user and finalizes the transaction.
+
+ \qml
+ Store {
+ id: store
+ Product {
+ id: healthPotionProduct
+ identifier: "healthPotion"
+ type: Product.Consumable
+
+ property bool purchasing: false
+
+ onPurchaseSucceeded: {
+ if (!hasAlreadyStoredTransaction(transaction.orderId)) {
+ ++healthPotions
+ if (!addHealthPotionToPersistentStorage(transaction.orderId)) {
+ popupErrorDialog(qsTr("Unable to write to persistent storage. Please make sure there is sufficient space and restart."))
+ } else {
+ transaction.finalize()
+ }
+ }
+
+ // Reset purchasing flag
+ purchasing = false
+ }
+
+ onPurchaseFailed: {
+ popupErrorDialog(qsTr("Purchase not completed."))
+ transaction.finalize()
+
+ // Reset purchasing flag
+ purchasing = false
+ }
+ }
+ }
+ \endqml
+
+ If a transaction is not finalized, it will be called again for the same transaction the next time the application
+ starts up, providing another chance to store the data. The transaction for a consumable product has
+ to be finalized before the product can be purchased again.
+
+ \section1 Purchasing a product
+
+ In order to purchase a product, call the object's purchase() method. This launches a platform-specific, asynchronous
+ process to purchase the product, for example by requesting the user's password and confirmation of the purchase.
+ In most cases, you should make sure that the application UI is not accepting input while the purchasing request
+ is being processed, as this is not handled automatically on all platforms.
+
+ The following example adds a button to be used with the example product in the previous section:
+ \qml
+ Rectangle {
+ id: button
+ width: 100
+ height: 50
+
+ Text {
+ anchors.centerIn: parent
+ text: qsTr("Buy health potion for only " + healthPotionProduct.price + "!")
+ }
+
+ MouseArea {
+ enabled: !healthPotionProduct.purchasing && healthPotionProduct.status === Product.Registered
+ anchors.fill: parent
+ onClicked: {
+ healthPotionProduct.purchasing = true
+ healthPotionProduct.purchase()
+ }
+ }
+ }
+ \endqml
+
+ When the button is clicked, the purchase process is started. At some point in the future, either the
+ \l{QtPurchasing::Product::onPurchaseFailed}{onPurchaseFailed} handler will be called (for example if the user cancels the transaction), or the
+ \l{QtPurchasing::Product::onPurchaseSucceeded}{onPurchaseSucceeded} handler will be called.
+
+ \note The button is only enabled if the product's status is set to Registered. The registration process
+ for a product is asynchronous, so purchases attempted on a product before it has been successfully registered
+ will always fail.
+
+ \section1 Restoring previously purchased products
+
+ If the application is uninstalled and subsequently reinstalled (or installed by the same user on
+ a different device) you should provide a way to restore the previously purchased unlockable products
+ in the external market place.
+
+ To start the process of restoring purchases, you should call the restorePurchases() method in the
+ \l Store object. This will cause the onPurchaseRestored handler to be called in each of the application's
+ unlockable products that has previously been purchased by the current user.
+
+ Continuing on the example from before, which could be some sort of role-playing computer game, lets imagine
+ that the game has downloadable content that you can buy to expand the game further. This should be an unlockable product,
+ because the user should not have to purchase it more than once.
+
+ \qml
+ Store {
+ id: store
+
+ // ... other products
+
+ Product {
+ id: dlcForestOfFooBarProduct
+ identifier: "dlcForestOfFooBar"
+ type: Product.Unlockable
+
+ property bool purchasing: false
+
+ onPurchaseSucceeded: {
+ if (!hasMap("forestOfFooBar.map")) {
+ if (!downloadExtraMap("forestOfFooBar.map")) {
+ popupErrorDialog(qsTr("Unable to download The Forest of FooBar map. Please make sure there is sufficient space and restart."))
+ } else {
+ transaction.finalize()
+ }
+ }
+
+ // Reset purchasing flag
+ purchasing = false
+ }
+
+ onPurchaseFailed: {
+ popupErrorDialog(qsTr("Purchase not completed."))
+ transaction.finalize()
+
+ // Reset purchasing flag
+ purchasing = false
+ }
+
+ onPurchaseRestored: {
+ if (!hasMap("forestOfFooBar.map")) {
+ if (!downloadExtraMap("forestOfFooBar.map")) {
+ popupErrorDialog(qsTr("Unable to download The Forest of FooBar map. Please make sure there is sufficient space and restart."))
+ } else {
+ transaction.finalize()
+ }
+ }
+ }
+ }
+ }
+ \endqml
+
+ If a user buys the downloadable content and later either installs the game on another device or uninstalls and reinstalls the game,
+ you can provide a way to restore the purchase, such as the following button:
+
+ \qml
+ Rectangle {
+ id: restoreButton
+ width: 100
+ height: 50
+
+ Text {
+ anchors.centerIn: parent
+ text: "Restore previously purchased content"
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ store.restorePurchases()
+ }
+ }
+ }
+ \endqml
+
+ Restoring purchases should always be done as a reaction to user input, as it may present a password dialog on some platforms.
+ Calling the restorePurchases() method launches the restore process asynchronously. At some point in the future the onPurchaseRestored
+ handler will be called if the product has previously been purchased.
+
+ \note While the function behaves as documented on Android, this functionality is technically not needed there. The reason for this
+ is that the Android device manages all unlockable purchases with no intervention from the application. If an application is
+ uninstalled and reinstalled (or installed on a different device) on Android, then onPurchaseSucceeded will be called for each previously
+ purchased, unlockable product when the application starts up.
+*/
+
diff --git a/examples/demos/hangman/doc/src/googleplay.qdoc b/examples/demos/hangman/doc/src/googleplay.qdoc
new file mode 100644
index 000000000..83b0a1168
--- /dev/null
+++ b/examples/demos/hangman/doc/src/googleplay.qdoc
@@ -0,0 +1,112 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:FDL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Free Documentation License Usage
+** Alternatively, this file may be used under the terms of the GNU Free
+** Documentation License version 1.3 as published by the Free Software
+** Foundation and appearing in the file included in the packaging of
+** this file. Please review the following information to ensure
+** the GNU Free Documentation License version 1.3 requirements
+** will be met: https://www.gnu.org/licenses/fdl-1.3.html.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*!
+
+ \page qtpurchasing-googleplay.html
+ \title Registering Products in Google Play
+ \brief A short guide to registering in-app products in Google Play Store.
+
+ The Google Play market place allows you to register your application, as well
+ as the in-app products you want to make available to it, without publishing
+ it. The following \l{https://developer.android.com/studio/publish}{this guide} gives a brief introduction on uploading your
+ application to Google Play and registering products.
+
+ \section1 Preparing your application for submission
+
+ Before you can upload your application to Google Play, you must prepare it
+ for submission.
+
+ \section2 Adding a Manifest XML
+
+ The default \c AndroidManifest.xml generated for applications by Qt is
+ suitable for development testing, but cannot be used when submitting the
+ application to Google Play.
+
+ Create your own manifest by clicking the
+ \uicontrol{Create Templates} button in Qt Creator. You can find the
+ button under \uicontrol{Projects} > \uicontrol{Build} tab. Expand
+ \uicontrol{Build Android APK} to see it.
+
+ Once the manifest is added to your project, you can modify it. The most
+ important parts are the \c{application name} and the \c{package name}. The
+ package name must be unique, and it is recommended to follow the
+ \b {com.mycompany.myappname} naming convention. The "com.mycompany"
+ namespace is based on the internet domain ownership to avoid naming
+ collisions with other applications.
+
+ Other important parts of the manifest include the \c{versionCode}, which must
+ be incremented every time you upload a new version of the application. Other
+ properties will decide how your application package
+ is presented in the store listing, such as the \c{application name} and the \c{version name}.
+
+ For more information on the \c AndroidManifest.xml, see the
+ \l{http://developer.android.com/guide/topics/manifest/manifest-element.html}{Android documentation}.
+
+ For more information, refer to the
+ \l{https://doc.qt.io/qt-5/android-3rdparty-libs.html}{Third-party Android Libraries}.
+
+ \section2 Signing the application
+
+ Qt uses the default debug key to sign your application to enable running the
+ application on a device. Same key cannot be used to sign the Android Application Bundles
+ that is meant to be published on Google Play.
+
+ See \l{https://doc.qt.io/qtcreator/creator-deploying-android.html#signing-android-packages}{Signing Android Packages}
+
+ \section1 Registering your application
+
+ Once the application is prepared for publishing, you can create a listing for
+ it in Google Play.
+
+ The first step is to get a publisher account, if you do not already have one.
+ Go to \l{https://play.google.com/apps/publish/}{the Google Play developer console},
+ log in with the Google account of your choice, and follow the steps as
+ directed.
+
+ When you have set up your account, click on \uicontrol{Add new application}
+ in Google Play's developer console.
+
+ Fill out as much information as you want in the store listing, and also the
+ \uicontrol{Pricing and distribution} page.
+
+ \section2 Publishing your application
+
+ In order to test in-app purchases in your application, you first have to
+ publish it.
+
+ See \l{https://developer.android.com/studio/publish}{Publish your app}
+
+ \section2 Adding in-app products
+
+ In order to access in-app products from your application, you must register
+ them in Google Play.
+
+ See \l{https://developer.android.com/google/play/billing/getting-ready#products}{Create and configure your products}
+
+*/
+
diff --git a/examples/demos/hangman/doc/src/iosclasses.qdoc b/examples/demos/hangman/doc/src/iosclasses.qdoc
new file mode 100644
index 000000000..f717352ed
--- /dev/null
+++ b/examples/demos/hangman/doc/src/iosclasses.qdoc
@@ -0,0 +1,92 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:FDL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Free Documentation License Usage
+** Alternatively, this file may be used under the terms of the GNU Free
+** Documentation License version 1.3 as published by the Free Software
+** Foundation and appearing in the file included in the packaging of
+** this file. Please review the following information to ensure
+** the GNU Free Documentation License version 1.3 requirements
+** will be met: https://www.gnu.org/licenses/fdl-1.3.html.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*!
+ \page qtpurchasing-iosclasses.html
+ \title Demo iOS Classes
+ \brief IOS classes are used in the purchasing demo to enable purchasing in iOS.
+ \ingroup qtpurchasing-examples
+
+ \section1 Classes
+ \table
+ \row
+ \li \l IosInAppPurchaseProduct
+ \li A product registered in the store.
+ \row
+ \li \l IosInAppPurchaseTransaction
+ \li Contains information about a transaction in the external app store.
+ \row
+ \li \l IosInAppPurchaseBackend
+ \li Comminucates with external store.
+ \endtable
+
+ Check out \l {Base Classes} which include InAppProduct, InAppStore,
+ InAppTransaction and InAppPurchaseBackend
+
+ \section1 IosInAppPurchaseProduct
+
+ IosInAppPurchaseProduct adds purchase() for initializing purchasing process.
+
+ \section1 IosInAppPurchaseTransaction
+
+ IosInAppPurchaseTransaction adds new parameters and return types.
+ Transaction error handling is implemented on IosInAppPurchaseTransaction's
+ constructor. The class has the finalize() function that finalizes the transaction.
+
+ \section1 IosInAppPurchaseBackend
+
+ IosInAppPurchaseBackend uses \b Objective-C language to communicate with the App
+ Store.
+
+ \section2 Initializing the products
+
+ This class shows the product's information on the store page.
+
+ \list 1
+ \li IosInAppPurchaseBackend is called from InAppStore::setupBackend().
+ \li At initialization IosInAppPurchaseBackend creates InAppPurchaseManager with
+ Objective-C.
+ \li InAppStore::registerProduct(productType, &identifier) function is called and
+ queryProduct() is executed in the back end.
+ \li Objective-C function requestProductData:() is called and will make \l{https://developer.apple.com/documentation/storekit/skproductsrequest}{SKProductRequest}.
+ \li After SKProductRequest is finished and product objects has been
+ created in Objective-C productsRequest:(), products will be registered
+ by the IosInAppPurchaseBackend::registerProduct() function.
+ \li Signal productQueryDone() will be emitted and the item's information will be
+ visible on the application's store page.
+ \endlist
+
+ \section2 Purchasing process
+
+ \list 1
+ \li The user presses one of the products on the applications store page.
+ \li \c IosInAppPurchaseProduct::purchase() function is called and payment is added to
+ the \c SKPaymentQueue in the Objective-C function paymentQueue:() in
+ IosInAppPurchaseBackend.
+ \li Purchase confirmation pop-up is launched for the user.
+ \endlist
+*/
diff --git a/examples/demos/hangman/doc/src/qtpurchasing-overview.qdoc b/examples/demos/hangman/doc/src/qtpurchasing-overview.qdoc
new file mode 100644
index 000000000..33790c8b2
--- /dev/null
+++ b/examples/demos/hangman/doc/src/qtpurchasing-overview.qdoc
@@ -0,0 +1,129 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:FDL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Free Documentation License Usage
+** Alternatively, this file may be used under the terms of the GNU Free
+** Documentation License version 1.3 as published by the Free Software
+** Foundation and appearing in the file included in the packaging of
+** this file. Please review the following information to ensure
+** the GNU Free Documentation License version 1.3 requirements
+** will be met: https://www.gnu.org/licenses/fdl-1.3.html.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*!
+ \example demos/hangman
+ \title In-App purchasing demo
+ \brief A complete mobile application that demonstrates purchasing in-app products.
+ \ingroup qtpurchasing-examples
+ \ingroup android-examples
+
+
+ \section1 What is this demo?
+ \image qthangman-example.png
+
+ This demo is a complete mobile application that demonstrates how it is
+ possible to offer in-app products inside a Qt application, in a
+ cross-platform manner. In order to test the in-app purchase functionality in the
+ example, you must first register the application and its products in the
+ external store. For an introduction on how to do this, see the guides for
+ \l{Registering Products in Google Play}{Google Play} and
+ \l{Registering Products in App Store}{App Store} respectively.
+
+ \section1 3rd party app stores
+
+ The in-app products must be registered in the target stores, before they
+ can be queried or purchased in an application. We recommend using the same
+ identifiers for the products in each store, as it simplifies the code to
+ query and purchase the products.
+
+ \list
+ \li \l{Registering Products in Google Play}
+ \li \l{Registering Products in App Store}
+ \endlist
+
+ \section1 How does the demo work
+ The demo is a QML application that registers QML types to access information
+ about in-app products, as well as to request purchases for those products.
+ These are registered in the external store for the target platform
+
+ In-app purchasing are added to application by first adding a Store object. In
+ the demo the Store object is created by the MainView component that is loaded
+ on application startup.
+ \snippet demos/hangman/qml/MainView.qml 0
+
+ The demo defines a component for displaying a store for purchasing in-app
+ products made available. These products must be first registered with the
+ store object we created above in MainView. There are two products available,
+ the first being a consumable type.
+
+ \snippet demos/hangman/qml/StoreView.qml 0
+
+ This consumable product provides 100 additional vowels to be used when
+ guessing words in the game. When it is successfully purchased, we update the
+ state of the application to include 100 additional vowels. Then we call
+ finalize() on the transaction object to confirm to the platform store that the
+ consumable product has been provided.
+
+ The second product is a non-consumable type that will unlock vowels permanently
+ in the future. In addition to updating the application state on purchase, we
+ must make sure to provide a way to restore this purchase on other devices used
+ by the end user. In this case we create a signal handler for onPurchaseRestored.
+
+ \snippet demos/hangman/qml/StoreView.qml 1
+
+ \image qthangman-store-example.png
+
+ In addition to registering the products, the demo also provide an interface to
+ actually purchase the registered product. The demo defines a custom component
+ called \c StoreItem to display and handle the purchasing interaction.
+
+ \snippet demos/hangman/qml/StoreView.qml 1
+
+ The StoreItem component will display the product data that is queried from the
+ platform's store, and will call the purchase() method on the product when it is
+ clicked by the user.
+
+ \snippet demos/hangman/qml/StoreItem.qml 0
+
+ If you are planning to use QML in your project with purchasing functionality,
+ check out
+ \l{Getting Started with Qt Purchasing in QML}.
+
+ Android and iOS use the base classes. From base classes there are derivative
+ classes for android and ios:
+
+ \list
+ \li \l{Base Classes}
+ \li \l{Demo Android Classes}
+ \li \l{Demo iOS Classes}
+ \endlist
+
+ \section1 In-App purchases
+
+ In-app purchases are a way to monetize an application. These purchases are
+ made from inside the application and can include anything from unlocking
+ content to virtual items. The demo uses the system APIs
+ for in-app purchases, which means the purchase process is more familiar to
+ the user, and the information already stored by the platform (such as credit
+ card information) can be used to simplify the purchase process.
+
+ \section1 Licenses and attributions
+
+ In regards to deploying the demo on Android see
+ \l Android GNU C++ Run-time Licensing for more information.
+*/
diff --git a/examples/demos/hangman/hangman.pro b/examples/demos/hangman/hangman.pro
new file mode 100644
index 000000000..bee39d28b
--- /dev/null
+++ b/examples/demos/hangman/hangman.pro
@@ -0,0 +1,21 @@
+QT += quick qml quickcontrols2
+
+INCLUDEPATH += purchasing/qmltypes \
+ purchasing/inapp
+
+CONFIG += qmltypes
+QML_IMPORT_PATH = $$PWD/hangmangame.h
+QML_IMPORT_NAME = Hangman
+QML_IMPORT_MAJOR_VERSION = 1
+
+SOURCES += $$PWD/main.cpp \
+ $$PWD/hangmangame.cpp
+
+HEADERS += $$PWD/hangmangame.h
+
+RESOURCES += resources.qrc
+
+target.path = $$[QT_INSTALL_EXAMPLES]/demos/hangman
+INSTALLS += target
+
+include($$PWD/purchasing/purchasing.pri)
diff --git a/examples/demos/hangman/hangmangame.cpp b/examples/demos/hangman/hangmangame.cpp
new file mode 100644
index 000000000..85f857ac0
--- /dev/null
+++ b/examples/demos/hangman/hangmangame.cpp
@@ -0,0 +1,278 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QFile>
+#include <QDebug>
+#include <QBuffer>
+#include <QRandomGenerator>
+#include <QtConcurrent/QtConcurrentRun>
+#include <mutex>
+
+#include "hangmangame.h"
+
+HangmanGame::HangmanGame(QObject *parent)
+ : QObject(parent)
+ , m_vowelsUnlocked(false)
+{
+ connect(this, &HangmanGame::vowelBought, this, &HangmanGame::registerLetterBought);
+ (void) QtConcurrent::run(&HangmanGame::initWordList, this);
+
+ m_vowelsUnlocked = m_persistentSettings.value("Hangman/vowelsUnlocked", false).toBool();
+ m_vowelsAvailable = m_persistentSettings.value("Hangman/vowelsAvailable", 0).toInt();
+ m_wordsGiven = m_persistentSettings.value("Hangman/wordsGiven", 0).toInt();
+ m_wordsGuessedCorrectly = m_persistentSettings.value("Hangman/wordsGuessedCorrectly", 0).toInt();
+ m_score = m_persistentSettings.value("Hangman/score", 0).toInt();
+}
+
+void HangmanGame::reset()
+{
+ m_lettersOwned.clear();
+ emit lettersOwnedChanged();
+ emit errorCountChanged();
+ chooseRandomWord();
+}
+
+void HangmanGame::reveal()
+{
+ m_lettersOwned += vowels() + consonants();
+ emit errorCountChanged();
+ emit lettersOwnedChanged();
+}
+
+void HangmanGame::gameOverReveal()
+{
+ m_lettersOwned += vowels() + consonants();
+ emit lettersOwnedChanged();
+}
+
+void HangmanGame::requestLetter(const QString &letterString)
+{
+ Q_ASSERT(letterString.size() == 1);
+ QChar letter = letterString.at(0);
+ registerLetterBought(letter);
+}
+
+void HangmanGame::guessWord(const QString &word)
+{
+ if (word.compare(m_word, Qt::CaseInsensitive) == 0) {
+ //Determine how many vowels were earned
+ setVowelsAvailable(m_vowelsAvailable + calculateEarnedVowels());
+ //score is number of remaining consonants + remaining errors
+ setScore(m_score + calculateEarnedPoints());
+ m_lettersOwned += m_word.toUpper();
+ } else {
+ // Small hack to get an additional penalty for guessing wrong
+ static int i=0;
+ Q_ASSERT(i < 10);
+ m_lettersOwned += QString::number(i++);
+ emit errorCountChanged();
+ }
+ emit lettersOwnedChanged();
+}
+
+bool HangmanGame::isVowel(const QString &letter)
+{
+ Q_ASSERT(letter.size() == 1);
+ QChar letterChar = letter.at(0);
+ return vowels().contains(letterChar);
+}
+
+QString HangmanGame::vowels() const
+{
+ return QStringLiteral("AEIOU");
+}
+
+QString HangmanGame::consonants() const
+{
+ return QStringLiteral("BCDFGHJKLMNPQRSTVWXYZ");
+}
+
+int HangmanGame::errorCount() const
+{
+ int count = 0;
+ for (QChar c : m_lettersOwned) {
+ if (!m_word.contains(c))
+ ++count;
+ }
+ return count;
+}
+
+bool HangmanGame::vowelsUnlocked() const
+{
+ return m_vowelsUnlocked;
+}
+
+void HangmanGame::setVowelsUnlocked(bool vowelsUnlocked)
+{
+ if (m_vowelsUnlocked != vowelsUnlocked) {
+ m_vowelsUnlocked = vowelsUnlocked;
+ m_persistentSettings.setValue("Hangman/vowelsUnlocked", m_vowelsUnlocked);
+ emit vowelsUnlockedChanged(m_vowelsUnlocked);
+ }
+}
+
+int HangmanGame::vowelsAvailable() const
+{
+ return m_vowelsAvailable;
+}
+
+int HangmanGame::wordsGiven() const
+{
+ return m_wordsGiven;
+}
+
+int HangmanGame::wordsGuessedCorrectly() const
+{
+ return m_wordsGuessedCorrectly;
+}
+
+int HangmanGame::score() const
+{
+ return m_score;
+}
+
+void HangmanGame::setScore(int score)
+{
+ if (m_score != score) {
+ m_score = score;
+ m_persistentSettings.setValue("Hangman/score", m_score);
+ emit scoreChanged(score);
+ }
+}
+
+void HangmanGame::setWordsGiven(int count)
+{
+ if (m_wordsGiven != count) {
+ m_wordsGiven = count;
+ m_persistentSettings.setValue("Hangman/wordsGiven", m_wordsGiven);
+ emit wordsGivenChanged(count);
+ }
+}
+
+void HangmanGame::setWordsGuessedCorrectly(int count)
+{
+ if (m_wordsGuessedCorrectly != count) {
+ m_wordsGuessedCorrectly = count;
+ m_persistentSettings.setValue("Hangman/wordsGuessedCorrectly", m_wordsGuessedCorrectly);
+ emit wordsGuessedCorrectlyChanged(count);
+ }
+}
+
+void HangmanGame::setVowelsAvailable(int count)
+{
+ if (m_vowelsAvailable != count) {
+ m_vowelsAvailable = count;
+ m_persistentSettings.setValue("Hangman/vowelsAvailable", m_vowelsAvailable);
+ emit vowelsAvailableChanged(count);
+ }
+}
+
+void HangmanGame::registerLetterBought(const QChar &letter)
+{
+ if (m_lettersOwned.contains(letter))
+ return;
+
+ m_lettersOwned.append(letter);
+ emit lettersOwnedChanged();
+
+ if (!m_word.contains(letter))
+ emit errorCountChanged();
+}
+
+void HangmanGame::chooseRandomWord()
+{
+ const std::lock_guard<QRecursiveMutex> locker(m_lock);
+ if (m_wordList.isEmpty())
+ return;
+
+ m_word = m_wordList.at(QRandomGenerator::global()->bounded(m_wordList.size()));
+ emit wordChanged();
+}
+
+void HangmanGame::initWordList()
+{
+ const std::lock_guard<QRecursiveMutex> locker(m_lock);
+ QFile file(":/dict.txt");
+ if (file.open(QIODevice::ReadOnly)) {
+ QByteArray allData = file.readAll();
+ QBuffer buffer(&allData);
+ if (!buffer.open(QIODevice::ReadOnly))
+ qFatal("Couldn't open buffer for reading!");
+
+ while (!buffer.atEnd()) {
+ QByteArray ba = buffer.readLine().trimmed().toUpper();
+ if (!ba.isEmpty() && ba.length() < 10)
+ m_wordList.append(QString::fromLatin1(ba));
+ }
+ }
+
+ chooseRandomWord();
+}
+
+int HangmanGame::calculateEarnedVowels()
+{
+ int total = 0;
+ for (int i = 0; i < m_word.length(); ++i) {
+ if (isVowel(QString(m_word[i])) && !m_lettersOwned.contains(QString(m_word[i])))
+ total++;
+ }
+ return total;
+}
+
+int HangmanGame::calculateEarnedPoints()
+{
+ int total = 0;
+ for (int i = 0; i < m_word.length(); ++i) {
+ if (consonants().contains(QString(m_word[i])) && !m_lettersOwned.contains(QString(m_word[i])))
+ total++;
+ }
+ total += 8 - errorCount();
+ return total;
+}
diff --git a/examples/demos/hangman/hangmangame.h b/examples/demos/hangman/hangmangame.h
new file mode 100644
index 000000000..aef323c8f
--- /dev/null
+++ b/examples/demos/hangman/hangmangame.h
@@ -0,0 +1,140 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef HANGMANGAME_H
+#define HANGMANGAME_H
+
+#include <QObject>
+#include <QStringList>
+#include <QMutex>
+#include <QSettings>
+#include <QtQml/qqml.h>
+
+#include "purchasing/inapp/inappproduct.h"
+#include "purchasing/inapp/inapppurchasebackend.h"
+#include "purchasing/inapp/inappstore.h"
+#include "purchasing/inapp/inapptransaction.h"
+#include "purchasing/qmltypes/inappproductqmltype.h"
+#include "purchasing/qmltypes/inappstoreqmltype.h"
+
+class HangmanGame : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QString word READ word NOTIFY wordChanged)
+ Q_PROPERTY(QString lettersOwned READ lettersOwned NOTIFY lettersOwnedChanged)
+ Q_PROPERTY(QString vowels READ vowels CONSTANT)
+ Q_PROPERTY(QString consonants READ consonants CONSTANT)
+ Q_PROPERTY(int errorCount READ errorCount NOTIFY errorCountChanged)
+ Q_PROPERTY(bool vowelsUnlocked READ vowelsUnlocked WRITE setVowelsUnlocked NOTIFY vowelsUnlockedChanged)
+ Q_PROPERTY(int vowelsAvailable READ vowelsAvailable WRITE setVowelsAvailable NOTIFY vowelsAvailableChanged)
+ Q_PROPERTY(int wordsGiven READ wordsGiven WRITE setWordsGiven NOTIFY wordsGivenChanged)
+ Q_PROPERTY(int wordsGuessedCorrectly READ wordsGuessedCorrectly WRITE setWordsGuessedCorrectly NOTIFY wordsGuessedCorrectlyChanged)
+ Q_PROPERTY(int score READ score WRITE setScore NOTIFY scoreChanged)
+ QML_NAMED_ELEMENT(Hangman)
+
+public:
+ explicit HangmanGame(QObject *parent = 0);
+ Q_INVOKABLE void reset();
+ Q_INVOKABLE void reveal();
+ Q_INVOKABLE void gameOverReveal();
+ Q_INVOKABLE void requestLetter(const QString &letterString);
+ Q_INVOKABLE void guessWord(const QString &word);
+ Q_INVOKABLE bool isVowel(const QString &letter);
+ Q_INVOKABLE void setVowelsAvailable(int count);
+ Q_INVOKABLE void setWordsGiven(int count);
+ Q_INVOKABLE void setWordsGuessedCorrectly(int count);
+ Q_INVOKABLE void setScore(int score);
+
+ QString word() const { return m_word; }
+ QString lettersOwned() const { return m_lettersOwned; }
+ QString vowels() const;
+ QString consonants() const;
+ int errorCount() const;
+ bool vowelsUnlocked() const;
+ void setVowelsUnlocked(bool vowelsUnlocked);
+ int vowelsAvailable() const;
+ int wordsGiven() const;
+ int wordsGuessedCorrectly() const;
+ int score() const;
+
+signals:
+ void wordChanged();
+ void lettersOwnedChanged();
+ void errorCountChanged();
+ void vowelBought(const QChar &vowel);
+ void purchaseWasSuccessful(bool wasSuccessful);
+ void vowelsUnlockedChanged(bool unlocked);
+ void vowelsAvailableChanged(int arg);
+ void wordsGivenChanged(int arg);
+ void wordsGuessedCorrectlyChanged(int arg);
+ void scoreChanged(int arg);
+
+private slots:
+ void registerLetterBought(const QChar &letter);
+
+private:
+ void chooseRandomWord();
+ void initWordList();
+ int calculateEarnedVowels();
+ int calculateEarnedPoints();
+
+ QString m_word;
+ QString m_lettersOwned;
+ QStringList m_wordList;
+ QRecursiveMutex m_lock;
+ bool m_vowelsUnlocked;
+ QSettings m_persistentSettings;
+ int m_vowelsAvailable;
+ int m_wordsGiven;
+ int m_wordsGuessedCorrectly;
+ int m_score;
+};
+
+#endif // HANGMANGAME_H
diff --git a/examples/demos/hangman/main.cpp b/examples/demos/hangman/main.cpp
new file mode 100644
index 000000000..058e69fd0
--- /dev/null
+++ b/examples/demos/hangman/main.cpp
@@ -0,0 +1,67 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtQuick>
+#include <QtGui/QGuiApplication>
+#include <QtQml/QQmlApplicationEngine>
+
+#include "hangmangame.h"
+#include "purchasing/qmltypes/inappstoreqmltype.h"
+#include "purchasing/inapp/inappproduct.h"
+#include "purchasing/inapp/inapptransaction.h"
+
+int main(int argc, char *argv[])
+{
+ QGuiApplication app(argc, argv);
+
+ QQmlApplicationEngine engine(QUrl("qrc:/main.qml"));
+
+ return app.exec();
+}
diff --git a/examples/demos/hangman/main.qml b/examples/demos/hangman/main.qml
new file mode 100644
index 000000000..c9b514d12
--- /dev/null
+++ b/examples/demos/hangman/main.qml
@@ -0,0 +1,92 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick
+import QtQuick.Controls
+
+ApplicationWindow {
+ id: mainWindow
+ height: 480
+ width: 320
+ visible: true;
+
+ Rectangle {
+ id: mainRect
+ gradient: Gradient {
+ GradientStop {
+ position: 0.0
+ color: "#87E0FD"
+ }
+ GradientStop {
+ position: 0.4
+ color: "#53CBF1"
+ }
+ GradientStop {
+ position: 1.0
+ color: "#05ABE0"
+ }
+ }
+ anchors.fill: parent
+
+ Loader {
+ id: gameLoader
+ asynchronous: true
+ visible: status == Loader.Ready
+ anchors.fill: parent
+ }
+
+ Loader {
+ id: splashLoader
+ anchors.fill: parent
+ source: "qml/SplashScreen.qml"
+ onLoaded: gameLoader.source = "qml/MainView.qml";
+ }
+ }
+}
diff --git a/examples/demos/hangman/purchasing/android/androidinappproduct.cpp b/examples/demos/hangman/purchasing/android/androidinappproduct.cpp
new file mode 100644
index 000000000..8c968eeee
--- /dev/null
+++ b/examples/demos/hangman/purchasing/android/androidinappproduct.cpp
@@ -0,0 +1,71 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "androidinappproduct.h"
+#include "androidinapppurchasebackend.h"
+
+QT_BEGIN_NAMESPACE
+
+AndroidInAppProduct::AndroidInAppProduct(AndroidInAppPurchaseBackend *backend,
+ const QString &price,
+ const QString &title,
+ const QString &description,
+ ProductType productType,
+ const QString &identifier,
+ QObject *parent)
+ : InAppProduct(price, title, description, productType, identifier, parent)
+ , m_backend(backend)
+{
+}
+
+void AndroidInAppProduct::purchase()
+{
+ m_backend->purchaseProduct(this);
+}
diff --git a/examples/demos/hangman/purchasing/android/androidinappproduct.h b/examples/demos/hangman/purchasing/android/androidinappproduct.h
new file mode 100644
index 000000000..1e7b6eb7f
--- /dev/null
+++ b/examples/demos/hangman/purchasing/android/androidinappproduct.h
@@ -0,0 +1,76 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef ANDROIDINAPPPRODUCT_H
+#define ANDROIDINAPPPRODUCT_H
+
+#include "../inapp/inappproduct.h"
+
+class AndroidInAppPurchaseBackend;
+class AndroidInAppProduct : public InAppProduct
+{
+ Q_OBJECT
+public:
+ explicit AndroidInAppProduct(AndroidInAppPurchaseBackend *backend,
+ const QString &price,
+ const QString &title,
+ const QString &description,
+ ProductType productType,
+ const QString &identifier,
+ QObject *parent = 0);
+
+ QString getProductId();
+ void purchase();
+
+private:
+ AndroidInAppPurchaseBackend *m_backend;
+};
+
+#endif // ANDROIDINAPPPRODUCT_H
diff --git a/examples/demos/hangman/purchasing/android/androidinapppurchasebackend.cpp b/examples/demos/hangman/purchasing/android/androidinapppurchasebackend.cpp
new file mode 100644
index 000000000..63470137c
--- /dev/null
+++ b/examples/demos/hangman/purchasing/android/androidinapppurchasebackend.cpp
@@ -0,0 +1,332 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QDebug>
+
+#include <QJniEnvironment>
+#include <QtCore>
+
+#include "androidinapppurchasebackend.h"
+#include "androidinappproduct.h"
+#include "androidinapptransaction.h"
+#include "../inapp/inappstore.h"
+
+
+QT_BEGIN_NAMESPACE
+
+AndroidInAppPurchaseBackend::AndroidInAppPurchaseBackend(QObject *parent)
+ : InAppPurchaseBackend(parent)
+ , m_isReady(false)
+{
+ m_javaObject = QJniObject("org/qtproject/qt/android/purchasing/InAppPurchase",
+ "(Landroid/content/Context;J)V",
+ QNativeInterface::QAndroidApplication::context(),
+ this);
+
+ if (!m_javaObject.isValid()) {
+ qWarning("Cannot initialize IAP backend for Android due to missing dependency: InAppPurchase class");
+ return;
+ }
+}
+
+void AndroidInAppPurchaseBackend::initialize()
+{
+ m_javaObject.callMethod<void>("initializeConnection");
+}
+
+bool AndroidInAppPurchaseBackend::isReady() const
+{
+ QMutexLocker locker(&m_mutex);
+ return m_isReady;
+}
+
+void AndroidInAppPurchaseBackend::restorePurchases()
+{
+ for (const QString &purchasedUnlockeble : purchasedUnlockebles) {
+ InAppProduct *product = store()->registeredProduct(purchasedUnlockeble);
+ Q_ASSERT(product != 0);
+
+ checkFinalizationStatus(product, InAppTransaction::PurchaseRestored);
+ }
+
+}
+
+void AndroidInAppPurchaseBackend::queryProducts(const QList<Product> &products)
+{
+ QMutexLocker locker(&m_mutex);
+ QJniEnvironment environment;
+
+ QStringList newProducts;
+ for (int i = 0; i < products.size(); ++i) {
+ const Product &product = products.at(i);
+ if (m_productTypeForPendingId.contains(product.identifier)) {
+ qWarning("Product query already pending for %s", qPrintable(product.identifier));
+ continue;
+ }
+ else{
+ m_productTypeForPendingId[product.identifier] = product.productType;
+ newProducts.append(product.identifier);
+ }
+ }
+
+ if (newProducts.isEmpty())
+ return;
+
+ jclass cls = environment->FindClass("java/lang/String");
+ jobjectArray productIds = environment->NewObjectArray(newProducts.size(), cls, 0);
+ environment->DeleteLocalRef(cls);
+
+ for (int i = 0; i < newProducts.size(); ++i) {
+ QJniObject identifier = QJniObject::fromString(newProducts.at(i));
+ environment->SetObjectArrayElement(productIds, i, identifier.object());
+ }
+
+ m_javaObject.callMethod<void>("queryDetails",
+ "([Ljava/lang/String;)V",
+ productIds);
+ environment->DeleteLocalRef(productIds);
+}
+
+void AndroidInAppPurchaseBackend::queryProduct(InAppProduct::ProductType productType,
+ const QString &identifier)
+{
+ queryProducts(QList<Product>() << Product(productType, identifier));
+}
+
+void AndroidInAppPurchaseBackend::setPlatformProperty(const QString &propertyName, const QString &value)
+{
+ QMutexLocker locker(&m_mutex);
+ if (propertyName.compare(QStringLiteral("AndroidPublicKey"), Qt::CaseInsensitive) == 0) {
+ m_javaObject.callMethod<void>("setPublicKey",
+ "(Ljava/lang/String;)V",
+ QJniObject::fromString(value).object<jstring>());
+ }
+}
+
+void AndroidInAppPurchaseBackend::consumeTransaction(const QString &purchaseToken)
+{
+ QMutexLocker locker(&m_mutex);
+ m_javaObject.callMethod<void>("consumePurchase",
+ "(Ljava/lang/String;)V",
+ QJniObject::fromString(purchaseToken).object<jstring>());
+}
+
+void AndroidInAppPurchaseBackend::registerFinalizedUnlockable(const QString &purchaseToken)
+{
+ QMutexLocker locker(&m_mutex);
+ m_javaObject.callMethod<void>("acknowledgeUnlockablePurchase",
+ "(Ljava/lang/String;)V",
+ QJniObject::fromString(purchaseToken).object<jstring>());
+}
+
+bool AndroidInAppPurchaseBackend::transactionFinalizedForProduct(InAppProduct *product)
+{
+ Q_ASSERT(m_infoForPurchase.contains(product->identifier()));
+
+ if (product->productType() != InAppProduct::Consumable && purchasedUnlockebles.contains(product->identifier()))
+ {
+ return true;
+ }
+ return false;
+}
+
+void AndroidInAppPurchaseBackend::checkFinalizationStatus(InAppProduct *product,
+ InAppTransaction::TransactionStatus status)
+{
+ // Verifies the finalization status of an item based on the following logic:
+ // 1. If the item is not purchased yet, do nothing (it's either never been purchased, or it's a
+ // consumed consumable.
+ // 2. If the item is purchased, and it's a consumable, it's unfinalized. Emit a new transaction.
+ // Consumable items are consumed when they are finalized.
+ // 3. If the item is purchased, and it's an unlockable, check the local cache for finalized
+ // unlockable purchases. If it's not there, then the transaction is unfinalized. This means
+ // that if the cache gets deleted or corrupted, the worst-case scenario is that the transactions
+ // are republished.
+ QHash<QString, PurchaseInfo>::iterator it = m_infoForPurchase.find(product->identifier());
+
+ if (it == m_infoForPurchase.end()) {
+ return;
+ }
+
+ const PurchaseInfo &info = it.value();
+ if (transactionFinalizedForProduct(product)) {
+ AndroidInAppTransaction *transaction = new AndroidInAppTransaction(info.signature,
+ info.data,
+ info.purchaseToken,
+ info.orderId,
+ status,
+ product,
+ info.timestamp,
+ InAppTransaction::NoFailure,
+ QString(),
+ this);
+ emit transactionReady(transaction);
+ }
+}
+
+void AndroidInAppPurchaseBackend::registerProduct(const QString &productId,
+ const QString &price,
+ const QString &title,
+ const QString &description)
+{
+ QMutexLocker locker(&m_mutex);
+ QHash<QString, InAppProduct::ProductType>::iterator it = m_productTypeForPendingId.find(productId);
+ Q_ASSERT(it != m_productTypeForPendingId.end());
+
+ AndroidInAppProduct *product = new AndroidInAppProduct(this, price, title, description, it.value(), it.key(), this);
+
+ emit productQueryDone(product);
+}
+
+void AndroidInAppPurchaseBackend::registerPurchased(const QString &identifier,
+ const QString &signature,
+ const QString &data,
+ const QString &purchaseToken,
+ const QString &orderId,
+ const QDateTime &timestamp)
+{
+ QMutexLocker locker(&m_mutex);
+ m_infoForPurchase.insert(identifier, PurchaseInfo(signature, data, purchaseToken, orderId, timestamp));
+
+ QHash<QString, InAppProduct::ProductType>::iterator it = m_productTypeForPendingId.find(identifier);
+ if (it.value() == InAppProduct::Unlockable && !purchasedUnlockebles.contains(identifier))
+ purchasedUnlockebles.append(identifier);
+
+
+}
+
+void AndroidInAppPurchaseBackend::registerReady()
+{
+ QMutexLocker locker(&m_mutex);
+ m_isReady = true;
+ emit ready();
+}
+
+void AndroidInAppPurchaseBackend::purchaseProduct(AndroidInAppProduct *product)
+{
+ QMutexLocker locker(&m_mutex);
+ if (!m_javaObject.isValid()) {
+ purchaseFailed(product, InAppTransaction::ErrorOccurred, QStringLiteral("Java backend is not initialized"));
+ return;
+ }
+
+ int requestCode = 0;
+ while (m_activePurchaseRequests.contains(requestCode)) {
+ requestCode++;
+ if (requestCode == 0) {
+ qWarning("No available request code for purchase request.");
+ return;
+ }
+ }
+
+ m_activePurchaseRequests[requestCode] = product;
+
+ m_javaObject.callMethod<void>("launchBillingFlow",
+ "(Ljava/lang/String;I)V",
+ QJniObject::fromString(product->identifier()).object<jstring>(),
+ requestCode);
+}
+
+void AndroidInAppPurchaseBackend::purchaseFailed(int requestCode, int failureReason, const QString &errorString)
+{
+ QMutexLocker locker(&m_mutex);
+ InAppProduct *product = m_activePurchaseRequests.take(requestCode);
+ if (product == 0) {
+ qWarning("No product registered for requestCode %d", requestCode);
+ return;
+ }
+
+ purchaseFailed(product, failureReason, errorString);
+}
+
+void AndroidInAppPurchaseBackend::purchaseFailed(InAppProduct *product, int failureReason, const QString &errorString)
+{
+ InAppTransaction *transaction = new AndroidInAppTransaction(QString(),
+ QString(),
+ QString(),
+ QString(),
+ InAppTransaction::PurchaseFailed,
+ product,
+ QDateTime(),
+ InAppTransaction::FailureReason(failureReason),
+ errorString,
+ this);
+ emit transactionReady(transaction);
+}
+
+void AndroidInAppPurchaseBackend::purchaseSucceeded(int requestCode,
+ const QString &signature,
+ const QString &data,
+ const QString &purchaseToken,
+ const QString &orderId,
+ const QDateTime &timestamp)
+
+{
+ QMutexLocker locker(&m_mutex);
+ InAppProduct *product = m_activePurchaseRequests.take(requestCode);
+ if (product == 0) {
+ qWarning("No product registered for requestCode %d", requestCode);
+ return;
+ }
+
+ m_infoForPurchase.insert(product->identifier(), PurchaseInfo(signature, data, purchaseToken, orderId, timestamp));
+ InAppTransaction *transaction = new AndroidInAppTransaction(signature,
+ data,
+ purchaseToken,
+ orderId,
+ InAppTransaction::PurchaseApproved,
+ product,
+ timestamp,
+ InAppTransaction::NoFailure,
+ QString(),
+ this);
+ emit transactionReady(transaction);
+}
+QT_END_NAMESPACE
diff --git a/examples/demos/hangman/purchasing/android/androidinapppurchasebackend.h b/examples/demos/hangman/purchasing/android/androidinapppurchasebackend.h
new file mode 100644
index 000000000..0c66fb5e3
--- /dev/null
+++ b/examples/demos/hangman/purchasing/android/androidinapppurchasebackend.h
@@ -0,0 +1,145 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef ANDROIDINAPPPURCHASEBACKEND_H
+#define ANDROIDINAPPPURCHASEBACKEND_H
+
+#include <QMutex>
+#include <QSet>
+#include <QDateTime>
+#include <QJniObject>
+#include <QJniEnvironment>
+
+#include "../inapp/inapppurchasebackend.h"
+#include "../inapp/inappproduct.h"
+#include "../inapp/inapptransaction.h"
+
+QT_BEGIN_NAMESPACE
+class AndroidInAppProduct;
+class AndroidInAppPurchaseBackend : public InAppPurchaseBackend
+{
+ Q_OBJECT
+public:
+ explicit AndroidInAppPurchaseBackend(QObject *parent = 0);
+
+ void initialize();
+ bool isReady() const;
+
+ void queryProducts(const QList<Product> &products);
+ void queryProduct(InAppProduct::ProductType productType, const QString &identifier);
+ void restorePurchases();
+
+ void setPlatformProperty(const QString &propertyName, const QString &value);
+
+ void purchaseProduct(AndroidInAppProduct *product);
+
+ void consumeTransaction(const QString &purchaseToken);
+ void registerFinalizedUnlockable(const QString &identifier);
+
+ // Callbacks from Java
+ Q_INVOKABLE void registerProduct(const QString &productId,
+ const QString &price,
+ const QString &title,
+ const QString &description);
+ Q_INVOKABLE void registerPurchased(const QString &identifier,
+ const QString &signature,
+ const QString &data,
+ const QString &purchaseToken,
+ const QString &orderId,
+ const QDateTime &timestamp);
+ Q_INVOKABLE void purchaseSucceeded(int requestCode,
+ const QString &signature,
+ const QString &data,
+ const QString &purchaseToken,
+ const QString &orderId,
+ const QDateTime &timestamp);
+ Q_INVOKABLE void purchaseFailed(int requestCode,
+ int failureReason,
+ const QString &errorString);
+ Q_INVOKABLE void registerReady();
+
+private:
+ void checkFinalizationStatus(InAppProduct *product,
+ InAppTransaction::TransactionStatus status = InAppTransaction::PurchaseApproved);
+ bool transactionFinalizedForProduct(InAppProduct *product);
+ void purchaseFailed(InAppProduct *product,
+ int failureReason,
+ const QString &errorString);
+
+ struct PurchaseInfo
+ {
+ PurchaseInfo(const QString &signature_, const QString &data_, const QString &purchaseToken_, const QString &orderId_, const QDateTime &timestamp_)
+ : signature(signature_)
+ , data(data_)
+ , purchaseToken(purchaseToken_)
+ , orderId(orderId_)
+ , timestamp(timestamp_)
+ {
+ }
+
+ QString signature;
+ QString data;
+ QString purchaseToken;
+ QString orderId;
+ QDateTime timestamp;
+ };
+
+ mutable QRecursiveMutex m_mutex;
+ bool m_isReady;
+ QList<QString> purchasedUnlockebles;
+ QJniObject m_javaObject;
+ QScopedPointer<AndroidInAppPurchaseBackend> d;
+ QHash<QString, InAppProduct::ProductType> m_productTypeForPendingId;
+ QHash<QString, PurchaseInfo> m_infoForPurchase;
+ QHash<int, InAppProduct *> m_activePurchaseRequests;
+};
+QT_END_NAMESPACE
+
+#endif // ANDROIDINAPPPURCHASEBACKEND_H
diff --git a/examples/demos/hangman/purchasing/android/androidinapptransaction.cpp b/examples/demos/hangman/purchasing/android/androidinapptransaction.cpp
new file mode 100644
index 000000000..397336f2b
--- /dev/null
+++ b/examples/demos/hangman/purchasing/android/androidinapptransaction.cpp
@@ -0,0 +1,124 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "../inapp/inappproduct.h"
+#include "androidinapptransaction.h"
+#include "androidinapppurchasebackend.h"
+
+QT_BEGIN_NAMESPACE
+
+AndroidInAppTransaction::AndroidInAppTransaction(const QString &signature,
+ const QString &data,
+ const QString &purchaseToken,
+ const QString &orderId,
+ TransactionStatus status,
+ InAppProduct *product,
+ const QDateTime &timestamp,
+ FailureReason failureReason,
+ const QString &errorString,
+ QObject *parent)
+ : InAppTransaction(status, product, parent)
+ , m_signature(signature)
+ , m_data(data)
+ , m_purchaseToken(purchaseToken)
+ , m_orderId(orderId)
+ , m_timestamp(timestamp)
+ , m_errorString(errorString)
+ , m_failureReason(failureReason)
+{
+ Q_ASSERT(qobject_cast<AndroidInAppPurchaseBackend *>(parent) != 0);
+}
+
+QString AndroidInAppTransaction::orderId() const
+{
+ return m_orderId;
+}
+
+QDateTime AndroidInAppTransaction::timestamp() const
+{
+ return m_timestamp;
+}
+
+QString AndroidInAppTransaction::errorString() const
+{
+ return m_errorString;
+}
+
+InAppTransaction::FailureReason AndroidInAppTransaction::failureReason() const
+{
+ return m_failureReason;
+}
+
+QString AndroidInAppTransaction::platformProperty(const QString &propertyName) const
+{
+ if (propertyName.compare(QStringLiteral("AndroidSignature"), Qt::CaseInsensitive) == 0)
+ return m_signature;
+ else if (propertyName.compare(QStringLiteral("AndroidPurchaseData"), Qt::CaseInsensitive) == 0)
+ return m_data;
+ else
+ return InAppTransaction::platformProperty(propertyName);
+}
+
+void AndroidInAppTransaction::finalize()
+{
+ AndroidInAppPurchaseBackend *backend = qobject_cast<AndroidInAppPurchaseBackend *>(parent());
+ if (status() == PurchaseApproved || status() == PurchaseRestored) {
+ if (product()->productType() == InAppProduct::Consumable){
+ backend->consumeTransaction(m_purchaseToken);}
+ else if (product()->productType() == InAppProduct::Unlockable){
+ backend->registerFinalizedUnlockable(m_purchaseToken);}
+ else {
+ qWarning("Product type not implemented.");
+ }
+ }
+
+ deleteLater();
+}
+QT_END_NAMESPACE
diff --git a/examples/demos/hangman/purchasing/android/androidinapptransaction.h b/examples/demos/hangman/purchasing/android/androidinapptransaction.h
new file mode 100644
index 000000000..5bba856eb
--- /dev/null
+++ b/examples/demos/hangman/purchasing/android/androidinapptransaction.h
@@ -0,0 +1,90 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef ANDROIDINAPPTRANSACTION_H
+#define ANDROIDINAPPTRANSACTION_H
+
+
+#include "../inapp/inapptransaction.h"
+
+class AndroidInAppTransaction : public InAppTransaction
+{
+ Q_OBJECT
+public:
+ explicit AndroidInAppTransaction(const QString &signature,
+ const QString &data,
+ const QString &purchaseToken,
+ const QString &orderId,
+ TransactionStatus status,
+ InAppProduct *product,
+ const QDateTime &timestamp,
+ FailureReason failureReason,
+ const QString &errorString,
+ QObject *parent = 0);
+
+ void finalize();
+
+ QString orderId() const;
+ QString errorString() const;
+ FailureReason failureReason() const;
+ QDateTime timestamp() const;
+ QString platformProperty(const QString &propertyName) const;
+
+private:
+ QString m_signature;
+ QString m_data;
+ QString m_purchaseToken;
+ QString m_orderId;
+ QDateTime m_timestamp;
+ QString m_errorString;
+ FailureReason m_failureReason;
+};
+
+#endif // ANDROIDINAPPTRANSACTION_H
diff --git a/examples/demos/hangman/purchasing/android/androidjni.cpp b/examples/demos/hangman/purchasing/android/androidjni.cpp
new file mode 100644
index 000000000..03e6d752b
--- /dev/null
+++ b/examples/demos/hangman/purchasing/android/androidjni.cpp
@@ -0,0 +1,153 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtCore/qdatetime.h>
+#include <jni.h>
+#include <QJniObject>
+
+#include "androidinapppurchasebackend.h"
+
+QT_USE_NAMESPACE
+
+static void purchasedProductsQueried(JNIEnv *, jclass, jlong nativePointer)
+{
+ AndroidInAppPurchaseBackend *backend = reinterpret_cast<AndroidInAppPurchaseBackend *>(nativePointer);
+ QMetaObject::invokeMethod(backend,
+ "registerReady",
+ Qt::AutoConnection);
+}
+
+static void registerProduct(JNIEnv *, jclass, jlong nativePointer, jstring productId, jstring price, jstring title, jstring description)
+{
+ AndroidInAppPurchaseBackend *backend = reinterpret_cast<AndroidInAppPurchaseBackend *>(nativePointer);
+ QMetaObject::invokeMethod(backend,
+ "registerProduct",
+ Qt::AutoConnection,
+ Q_ARG(QString, QJniObject(productId).toString()),
+ Q_ARG(QString, QJniObject(price).toString()),
+ Q_ARG(QString, QJniObject(title).toString()),
+ Q_ARG(QString, QJniObject(description).toString()));
+}
+
+static void registerPurchased(JNIEnv *, jclass, jlong nativePointer, jstring identifier,
+ jstring signature, jstring data, jstring purchaseToken, jstring orderId, jlong timestamp)
+{
+ QDateTime dateTime = QDateTime::fromMSecsSinceEpoch(qint64(timestamp));
+ dateTime.setTimeSpec(Qt::LocalTime);
+
+ AndroidInAppPurchaseBackend *backend = reinterpret_cast<AndroidInAppPurchaseBackend *>(nativePointer);
+ QMetaObject::invokeMethod(backend,
+ "registerPurchased",
+ Qt::AutoConnection,
+ Q_ARG(QString, QJniObject(identifier).toString()),
+ Q_ARG(QString, QJniObject(signature).toString()),
+ Q_ARG(QString, QJniObject(data).toString()),
+ Q_ARG(QString, QJniObject(purchaseToken).toString()),
+ Q_ARG(QString, QJniObject(orderId).toString()),
+ Q_ARG(QDateTime, dateTime));
+}
+
+static void purchaseSucceeded(JNIEnv *, jclass, jlong nativePointer, jint requestCode,
+ jstring signature, jstring data, jstring purchaseToken, jstring orderId, jlong timestamp)
+{
+ QDateTime dateTime = QDateTime::fromMSecsSinceEpoch(qint64(timestamp));
+ dateTime.setTimeSpec(Qt::LocalTime);
+
+ AndroidInAppPurchaseBackend *backend = reinterpret_cast<AndroidInAppPurchaseBackend *>(nativePointer);
+ QMetaObject::invokeMethod(backend,
+ "purchaseSucceeded",
+ Qt::AutoConnection,
+ Q_ARG(int, int(requestCode)),
+ Q_ARG(QString, QJniObject(signature).toString()),
+ Q_ARG(QString, QJniObject(data).toString()),
+ Q_ARG(QString, QJniObject(purchaseToken).toString()),
+ Q_ARG(QString, QJniObject(orderId).toString()),
+ Q_ARG(QDateTime, dateTime));
+}
+
+static void purchaseFailed(JNIEnv *, jclass, jlong nativePointer, jint requestCode, jint failureReason, jstring errorString)
+{
+ AndroidInAppPurchaseBackend *backend = reinterpret_cast<AndroidInAppPurchaseBackend *>(nativePointer);
+ QMetaObject::invokeMethod(backend,
+ "purchaseFailed",
+ Qt::AutoConnection,
+ Q_ARG(int, int(requestCode)),
+ Q_ARG(int, int(failureReason)),
+ Q_ARG(QString, QJniObject(errorString).toString()));
+}
+
+static JNINativeMethod methods[] = {
+ {"purchasedProductsQueried", "(J)V", (void *)purchasedProductsQueried},
+ {"registerProduct", "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", (void *)registerProduct},
+ {"registerPurchased", "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;J)V", (void *)registerPurchased},
+ {"purchaseSucceeded", "(JILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;J)V", (void *)purchaseSucceeded},
+ {"purchaseFailed", "(JIILjava/lang/String;)V", (void *)purchaseFailed}
+};
+
+JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *)
+{
+ static bool initialized = false;
+ if (initialized){
+ return JNI_VERSION_1_6;}
+ initialized = true;
+
+ JNIEnv *env;
+ if (vm->GetEnv((void **)&env, JNI_VERSION_1_6) != JNI_OK){
+ return JNI_ERR;}
+
+ jclass clazz = env->FindClass("org/qtproject/qt/android/purchasing/InAppPurchase");
+ if (!clazz){
+ return JNI_ERR;}
+
+ if (env->RegisterNatives(clazz, methods, sizeof(methods) / sizeof(methods[0])) < 0){
+ return JNI_ERR;}
+
+ return JNI_VERSION_1_6;
+}
diff --git a/examples/demos/hangman/purchasing/android/build.gradle b/examples/demos/hangman/purchasing/android/build.gradle
new file mode 100644
index 000000000..b33486e1c
--- /dev/null
+++ b/examples/demos/hangman/purchasing/android/build.gradle
@@ -0,0 +1,79 @@
+buildscript {
+ repositories {
+ google()
+ mavenCentral()
+ }
+
+ dependencies {
+ classpath 'com.android.tools.build:gradle:4.1.3'
+ }
+}
+
+repositories {
+ google()
+ mavenCentral()
+}
+
+apply plugin: 'com.android.application'
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
+ implementation "com.android.billingclient:billing:4.0.0"
+}
+
+android {
+ /*******************************************************
+ * The following variables:
+ * - androidBuildToolsVersion,
+ * - androidCompileSdkVersion
+ * - qtAndroidDir - holds the path to qt android files
+ * needed to build any Qt application
+ * on Android.
+ *
+ * are defined in gradle.properties file. This file is
+ * updated by QtCreator and androiddeployqt tools.
+ * Changing them manually might break the compilation!
+ *******************************************************/
+
+ compileSdkVersion androidCompileSdkVersion.toInteger()
+
+ buildToolsVersion androidBuildToolsVersion
+
+ sourceSets {
+ main {
+ manifest.srcFile 'AndroidManifest.xml'
+ java.srcDirs = [qtAndroidDir + '/src', 'src', 'java']
+ aidl.srcDirs = [qtAndroidDir + '/src', 'src', 'aidl']
+ res.srcDirs = [qtAndroidDir + '/res', 'res']
+ resources.srcDirs = ['resources']
+ renderscript.srcDirs = ['src']
+ assets.srcDirs = ['assets']
+ jniLibs.srcDirs = ['libs']
+ }
+ }
+
+ tasks.withType(JavaCompile) {
+ options.incremental = true
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+ lintOptions {
+ abortOnError false
+ }
+
+ // Do not compress Qt binary resources file
+ aaptOptions {
+ noCompress 'rcc'
+ }
+
+ defaultConfig {
+ resConfig "en"
+ minSdkVersion qtMinSdkVersion
+ targetSdkVersion qtTargetSdkVersion
+ ndk.abiFilters = qtTargetAbiList.split(",")
+ }
+}
diff --git a/examples/demos/hangman/purchasing/android/src/org/qtproject/qt/android/purchasing/Base64.java b/examples/demos/hangman/purchasing/android/src/org/qtproject/qt/android/purchasing/Base64.java
new file mode 100644
index 000000000..fb0605fa8
--- /dev/null
+++ b/examples/demos/hangman/purchasing/android/src/org/qtproject/qt/android/purchasing/Base64.java
@@ -0,0 +1,572 @@
+/* Copyright (c) 2012 Google Inc.
+ * Copyright (c) 2015 The Qt Company Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.qtproject.qt.android.purchasing;
+
+// This code was converted from code at http://iharder.sourceforge.net/base64/
+// Lots of extraneous features were removed.
+/* The original code said:
+ * <p>
+ * I am placing this code in the Public Domain. Do with it as you will.
+ * This software comes with no guarantees or warranties but with
+ * plenty of well-wishing instead!
+ * Please visit
+ * <a href="http://iharder.net/xmlizable">http://iharder.net/xmlizable</a>
+ * periodically to check for updates or to contribute improvements.
+ * </p>
+ *
+ * @author Robert Harder
+ * @author rharder@usa.net
+ * @version 1.3
+ */
+
+/**
+ * Base64 converter class. This code is not a complete MIME encoder;
+ * it simply converts binary data to base64 data and back.
+ *
+ * <p>Note {@link CharBase64} is a GWT-compatible implementation of this
+ * class.
+ */
+public class Base64 {
+ /** Specify encoding (value is {@code true}). */
+ public final static boolean ENCODE = true;
+
+ /** Specify decoding (value is {@code false}). */
+ public final static boolean DECODE = false;
+
+ /** The equals sign (=) as a byte. */
+ private final static byte EQUALS_SIGN = (byte) '=';
+
+ /** The new line character (\n) as a byte. */
+ private final static byte NEW_LINE = (byte) '\n';
+
+ /**
+ * The 64 valid Base64 values.
+ */
+ private final static byte[] ALPHABET =
+ {(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F',
+ (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K',
+ (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P',
+ (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U',
+ (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z',
+ (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e',
+ (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j',
+ (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o',
+ (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't',
+ (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y',
+ (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3',
+ (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8',
+ (byte) '9', (byte) '+', (byte) '/'};
+
+ /**
+ * The 64 valid web safe Base64 values.
+ */
+ private final static byte[] WEBSAFE_ALPHABET =
+ {(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F',
+ (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K',
+ (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P',
+ (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U',
+ (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z',
+ (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e',
+ (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j',
+ (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o',
+ (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't',
+ (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y',
+ (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3',
+ (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8',
+ (byte) '9', (byte) '-', (byte) '_'};
+
+ /**
+ * Translates a Base64 value to either its 6-bit reconstruction value
+ * or a negative number indicating some other meaning.
+ **/
+ private final static byte[] DECODABET = {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8
+ -5, -5, // Whitespace: Tab and Linefeed
+ -9, -9, // Decimal 11 - 12
+ -5, // Whitespace: Carriage Return
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
+ -9, -9, -9, -9, -9, // Decimal 27 - 31
+ -5, // Whitespace: Space
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42
+ 62, // Plus sign at decimal 43
+ -9, -9, -9, // Decimal 44 - 46
+ 63, // Slash at decimal 47
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
+ -9, -9, -9, // Decimal 58 - 60
+ -1, // Equals sign at decimal 61
+ -9, -9, -9, // Decimal 62 - 64
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N'
+ 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z'
+ -9, -9, -9, -9, -9, -9, // Decimal 91 - 96
+ 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm'
+ 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z'
+ -9, -9, -9, -9, -9 // Decimal 123 - 127
+ /* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */
+ };
+
+ /** The web safe decodabet */
+ private final static byte[] WEBSAFE_DECODABET =
+ {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8
+ -5, -5, // Whitespace: Tab and Linefeed
+ -9, -9, // Decimal 11 - 12
+ -5, // Whitespace: Carriage Return
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
+ -9, -9, -9, -9, -9, // Decimal 27 - 31
+ -5, // Whitespace: Space
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 44
+ 62, // Dash '-' sign at decimal 45
+ -9, -9, // Decimal 46-47
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
+ -9, -9, -9, // Decimal 58 - 60
+ -1, // Equals sign at decimal 61
+ -9, -9, -9, // Decimal 62 - 64
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N'
+ 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z'
+ -9, -9, -9, -9, // Decimal 91-94
+ 63, // Underscore '_' at decimal 95
+ -9, // Decimal 96
+ 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm'
+ 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z'
+ -9, -9, -9, -9, -9 // Decimal 123 - 127
+ /* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */
+ };
+
+ // Indicates white space in encoding
+ private final static byte WHITE_SPACE_ENC = -5;
+ // Indicates equals sign in encoding
+ private final static byte EQUALS_SIGN_ENC = -1;
+
+ /** Defeats instantiation. */
+ private Base64() {
+ }
+
+ /* ******** E N C O D I N G M E T H O D S ******** */
+
+ /**
+ * Encodes up to three bytes of the array <var>source</var>
+ * and writes the resulting four Base64 bytes to <var>destination</var>.
+ * The source and destination arrays can be manipulated
+ * anywhere along their length by specifying
+ * <var>srcOffset</var> and <var>destOffset</var>.
+ * This method does not check to make sure your arrays
+ * are large enough to accommodate <var>srcOffset</var> + 3 for
+ * the <var>source</var> array or <var>destOffset</var> + 4 for
+ * the <var>destination</var> array.
+ * The actual number of significant bytes in your array is
+ * given by <var>numSigBytes</var>.
+ *
+ * @param source the array to convert
+ * @param srcOffset the index where conversion begins
+ * @param numSigBytes the number of significant bytes in your array
+ * @param destination the array to hold the conversion
+ * @param destOffset the index where output will be put
+ * @param alphabet is the encoding alphabet
+ * @return the <var>destination</var> array
+ * @since 1.3
+ */
+ private static byte[] encode3to4(byte[] source, int srcOffset,
+ int numSigBytes, byte[] destination, int destOffset, byte[] alphabet) {
+ // 1 2 3
+ // 01234567890123456789012345678901 Bit position
+ // --------000000001111111122222222 Array position from threeBytes
+ // --------| || || || | Six bit groups to index alphabet
+ // >>18 >>12 >> 6 >> 0 Right shift necessary
+ // 0x3f 0x3f 0x3f Additional AND
+
+ // Create buffer with zero-padding if there are only one or two
+ // significant bytes passed in the array.
+ // We have to shift left 24 in order to flush out the 1's that appear
+ // when Java treats a value as negative that is cast from a byte to an int.
+ int inBuff =
+ (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0)
+ | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0)
+ | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0);
+
+ switch (numSigBytes) {
+ case 3:
+ destination[destOffset] = alphabet[(inBuff >>> 18)];
+ destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
+ destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f];
+ destination[destOffset + 3] = alphabet[(inBuff) & 0x3f];
+ return destination;
+ case 2:
+ destination[destOffset] = alphabet[(inBuff >>> 18)];
+ destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
+ destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f];
+ destination[destOffset + 3] = EQUALS_SIGN;
+ return destination;
+ case 1:
+ destination[destOffset] = alphabet[(inBuff >>> 18)];
+ destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
+ destination[destOffset + 2] = EQUALS_SIGN;
+ destination[destOffset + 3] = EQUALS_SIGN;
+ return destination;
+ default:
+ return destination;
+ } // end switch
+ } // end encode3to4
+
+ /**
+ * Encodes a byte array into Base64 notation.
+ * Equivalent to calling
+ * {@code encodeBytes(source, 0, source.length)}
+ *
+ * @param source The data to convert
+ * @since 1.4
+ */
+ public static String encode(byte[] source) {
+ return encode(source, 0, source.length, ALPHABET, true);
+ }
+
+ /**
+ * Encodes a byte array into web safe Base64 notation.
+ *
+ * @param source The data to convert
+ * @param doPadding is {@code true} to pad result with '=' chars
+ * if it does not fall on 3 byte boundaries
+ */
+ public static String encodeWebSafe(byte[] source, boolean doPadding) {
+ return encode(source, 0, source.length, WEBSAFE_ALPHABET, doPadding);
+ }
+
+ /**
+ * Encodes a byte array into Base64 notation.
+ *
+ * @param source the data to convert
+ * @param off offset in array where conversion should begin
+ * @param len length of data to convert
+ * @param alphabet the encoding alphabet
+ * @param doPadding is {@code true} to pad result with '=' chars
+ * if it does not fall on 3 byte boundaries
+ * @since 1.4
+ */
+ public static String encode(byte[] source, int off, int len, byte[] alphabet,
+ boolean doPadding) {
+ byte[] outBuff = encode(source, off, len, alphabet, Integer.MAX_VALUE);
+ int outLen = outBuff.length;
+
+ // If doPadding is false, set length to truncate '='
+ // padding characters
+ while (doPadding == false && outLen > 0) {
+ if (outBuff[outLen - 1] != '=') {
+ break;
+ }
+ outLen -= 1;
+ }
+
+ return new String(outBuff, 0, outLen);
+ }
+
+ /**
+ * Encodes a byte array into Base64 notation.
+ *
+ * @param source the data to convert
+ * @param off offset in array where conversion should begin
+ * @param len length of data to convert
+ * @param alphabet is the encoding alphabet
+ * @param maxLineLength maximum length of one line.
+ * @return the BASE64-encoded byte array
+ */
+ public static byte[] encode(byte[] source, int off, int len, byte[] alphabet,
+ int maxLineLength) {
+ int lenDiv3 = (len + 2) / 3; // ceil(len / 3)
+ int len43 = lenDiv3 * 4;
+ byte[] outBuff = new byte[len43 // Main 4:3
+ + (len43 / maxLineLength)]; // New lines
+
+ int d = 0;
+ int e = 0;
+ int len2 = len - 2;
+ int lineLength = 0;
+ for (; d < len2; d += 3, e += 4) {
+
+ // The following block of code is the same as
+ // encode3to4( source, d + off, 3, outBuff, e, alphabet );
+ // but inlined for faster encoding (~20% improvement)
+ int inBuff =
+ ((source[d + off] << 24) >>> 8)
+ | ((source[d + 1 + off] << 24) >>> 16)
+ | ((source[d + 2 + off] << 24) >>> 24);
+ outBuff[e] = alphabet[(inBuff >>> 18)];
+ outBuff[e + 1] = alphabet[(inBuff >>> 12) & 0x3f];
+ outBuff[e + 2] = alphabet[(inBuff >>> 6) & 0x3f];
+ outBuff[e + 3] = alphabet[(inBuff) & 0x3f];
+
+ lineLength += 4;
+ if (lineLength == maxLineLength) {
+ outBuff[e + 4] = NEW_LINE;
+ e++;
+ lineLength = 0;
+ } // end if: end of line
+ } // end for: each piece of array
+
+ if (d < len) {
+ encode3to4(source, d + off, len - d, outBuff, e, alphabet);
+
+ lineLength += 4;
+ if (lineLength == maxLineLength) {
+ // Add a last newline
+ outBuff[e + 4] = NEW_LINE;
+ e++;
+ }
+ e += 4;
+ }
+
+ assert (e == outBuff.length);
+ return outBuff;
+ }
+
+
+ /* ******** D E C O D I N G M E T H O D S ******** */
+
+
+ /**
+ * Decodes four bytes from array <var>source</var>
+ * and writes the resulting bytes (up to three of them)
+ * to <var>destination</var>.
+ * The source and destination arrays can be manipulated
+ * anywhere along their length by specifying
+ * <var>srcOffset</var> and <var>destOffset</var>.
+ * This method does not check to make sure your arrays
+ * are large enough to accommodate <var>srcOffset</var> + 4 for
+ * the <var>source</var> array or <var>destOffset</var> + 3 for
+ * the <var>destination</var> array.
+ * This method returns the actual number of bytes that
+ * were converted from the Base64 encoding.
+ *
+ *
+ * @param source the array to convert
+ * @param srcOffset the index where conversion begins
+ * @param destination the array to hold the conversion
+ * @param destOffset the index where output will be put
+ * @param decodabet the decodabet for decoding Base64 content
+ * @return the number of decoded bytes converted
+ * @since 1.3
+ */
+ private static int decode4to3(byte[] source, int srcOffset,
+ byte[] destination, int destOffset, byte[] decodabet) {
+ // Example: Dk==
+ if (source[srcOffset + 2] == EQUALS_SIGN) {
+ int outBuff =
+ ((decodabet[source[srcOffset]] << 24) >>> 6)
+ | ((decodabet[source[srcOffset + 1]] << 24) >>> 12);
+
+ destination[destOffset] = (byte) (outBuff >>> 16);
+ return 1;
+ } else if (source[srcOffset + 3] == EQUALS_SIGN) {
+ // Example: DkL=
+ int outBuff =
+ ((decodabet[source[srcOffset]] << 24) >>> 6)
+ | ((decodabet[source[srcOffset + 1]] << 24) >>> 12)
+ | ((decodabet[source[srcOffset + 2]] << 24) >>> 18);
+
+ destination[destOffset] = (byte) (outBuff >>> 16);
+ destination[destOffset + 1] = (byte) (outBuff >>> 8);
+ return 2;
+ } else {
+ // Example: DkLE
+ int outBuff =
+ ((decodabet[source[srcOffset]] << 24) >>> 6)
+ | ((decodabet[source[srcOffset + 1]] << 24) >>> 12)
+ | ((decodabet[source[srcOffset + 2]] << 24) >>> 18)
+ | ((decodabet[source[srcOffset + 3]] << 24) >>> 24);
+
+ destination[destOffset] = (byte) (outBuff >> 16);
+ destination[destOffset + 1] = (byte) (outBuff >> 8);
+ destination[destOffset + 2] = (byte) (outBuff);
+ return 3;
+ }
+ } // end decodeToBytes
+
+
+ /**
+ * Decodes data from Base64 notation.
+ *
+ * @param s the string to decode (decoded in default encoding)
+ * @return the decoded data
+ * @since 1.4
+ */
+ public static byte[] decode(String s) throws Base64DecoderException {
+ byte[] bytes = s.getBytes();
+ return decode(bytes, 0, bytes.length);
+ }
+
+ /**
+ * Decodes data from web safe Base64 notation.
+ * Web safe encoding uses '-' instead of '+', '_' instead of '/'
+ *
+ * @param s the string to decode (decoded in default encoding)
+ * @return the decoded data
+ */
+ public static byte[] decodeWebSafe(String s) throws Base64DecoderException {
+ byte[] bytes = s.getBytes();
+ return decodeWebSafe(bytes, 0, bytes.length);
+ }
+
+ /**
+ * Decodes Base64 content in byte array format and returns
+ * the decoded byte array.
+ *
+ * @param source The Base64 encoded data
+ * @return decoded data
+ * @since 1.3
+ * @throws Base64DecoderException
+ */
+ public static byte[] decode(byte[] source) throws Base64DecoderException {
+ return decode(source, 0, source.length);
+ }
+
+ /**
+ * Decodes web safe Base64 content in byte array format and returns
+ * the decoded data.
+ * Web safe encoding uses '-' instead of '+', '_' instead of '/'
+ *
+ * @param source the string to decode (decoded in default encoding)
+ * @return the decoded data
+ */
+ public static byte[] decodeWebSafe(byte[] source)
+ throws Base64DecoderException {
+ return decodeWebSafe(source, 0, source.length);
+ }
+
+ /**
+ * Decodes Base64 content in byte array format and returns
+ * the decoded byte array.
+ *
+ * @param source the Base64 encoded data
+ * @param off the offset of where to begin decoding
+ * @param len the length of characters to decode
+ * @return decoded data
+ * @since 1.3
+ * @throws Base64DecoderException
+ */
+ public static byte[] decode(byte[] source, int off, int len)
+ throws Base64DecoderException {
+ return decode(source, off, len, DECODABET);
+ }
+
+ /**
+ * Decodes web safe Base64 content in byte array format and returns
+ * the decoded byte array.
+ * Web safe encoding uses '-' instead of '+', '_' instead of '/'
+ *
+ * @param source the Base64 encoded data
+ * @param off the offset of where to begin decoding
+ * @param len the length of characters to decode
+ * @return decoded data
+ */
+ public static byte[] decodeWebSafe(byte[] source, int off, int len)
+ throws Base64DecoderException {
+ return decode(source, off, len, WEBSAFE_DECODABET);
+ }
+
+ /**
+ * Decodes Base64 content using the supplied decodabet and returns
+ * the decoded byte array.
+ *
+ * @param source the Base64 encoded data
+ * @param off the offset of where to begin decoding
+ * @param len the length of characters to decode
+ * @param decodabet the decodabet for decoding Base64 content
+ * @return decoded data
+ */
+ public static byte[] decode(byte[] source, int off, int len, byte[] decodabet)
+ throws Base64DecoderException {
+ int len34 = len * 3 / 4;
+ byte[] outBuff = new byte[2 + len34]; // Upper limit on size of output
+ int outBuffPosn = 0;
+
+ byte[] b4 = new byte[4];
+ int b4Posn = 0;
+ int i = 0;
+ byte sbiCrop = 0;
+ byte sbiDecode = 0;
+ for (i = 0; i < len; i++) {
+ sbiCrop = (byte) (source[i + off] & 0x7f); // Only the low seven bits
+ sbiDecode = decodabet[sbiCrop];
+
+ if (sbiDecode >= WHITE_SPACE_ENC) { // White space Equals sign or better
+ if (sbiDecode >= EQUALS_SIGN_ENC) {
+ // An equals sign (for padding) must not occur at position 0 or 1
+ // and must be the last byte[s] in the encoded value
+ if (sbiCrop == EQUALS_SIGN) {
+ int bytesLeft = len - i;
+ byte lastByte = (byte) (source[len - 1 + off] & 0x7f);
+ if (b4Posn == 0 || b4Posn == 1) {
+ throw new Base64DecoderException(
+ "invalid padding byte '=' at byte offset " + i);
+ } else if ((b4Posn == 3 && bytesLeft > 2)
+ || (b4Posn == 4 && bytesLeft > 1)) {
+ throw new Base64DecoderException(
+ "padding byte '=' falsely signals end of encoded value "
+ + "at offset " + i);
+ } else if (lastByte != EQUALS_SIGN && lastByte != NEW_LINE) {
+ throw new Base64DecoderException(
+ "encoded value has invalid trailing byte");
+ }
+ break;
+ }
+
+ b4[b4Posn++] = sbiCrop;
+ if (b4Posn == 4) {
+ outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet);
+ b4Posn = 0;
+ }
+ }
+ } else {
+ throw new Base64DecoderException("Bad Base64 input character at " + i
+ + ": " + source[i + off] + "(decimal)");
+ }
+ }
+
+ // Because web safe encoding allows non padding base64 encodes, we
+ // need to pad the rest of the b4 buffer with equal signs when
+ // b4Posn != 0. There can be at most 2 equal signs at the end of
+ // four characters, so the b4 buffer must have two or three
+ // characters. This also catches the case where the input is
+ // padded with EQUALS_SIGN
+ if (b4Posn != 0) {
+ if (b4Posn == 1) {
+ throw new Base64DecoderException("single trailing character at offset "
+ + (len - 1));
+ }
+ b4[b4Posn++] = EQUALS_SIGN;
+ outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet);
+ }
+
+ byte[] out = new byte[outBuffPosn];
+ System.arraycopy(outBuff, 0, out, 0, outBuffPosn);
+ return out;
+ }
+}
diff --git a/examples/demos/hangman/purchasing/android/src/org/qtproject/qt/android/purchasing/Base64DecoderException.java b/examples/demos/hangman/purchasing/android/src/org/qtproject/qt/android/purchasing/Base64DecoderException.java
new file mode 100644
index 000000000..884294712
--- /dev/null
+++ b/examples/demos/hangman/purchasing/android/src/org/qtproject/qt/android/purchasing/Base64DecoderException.java
@@ -0,0 +1,34 @@
+/* Copyright (c) 2012 Google Inc.
+ * Copyright (c) 2015 The Qt Company Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.qtproject.qt.android.purchasing;
+
+/**
+ * Exception thrown when encountering an invalid Base64 input character.
+ *
+ * @author nelson
+ */
+public class Base64DecoderException extends Exception {
+ public Base64DecoderException() {
+ super();
+ }
+
+ public Base64DecoderException(String s) {
+ super(s);
+ }
+
+ private static final long serialVersionUID = 1L;
+}
diff --git a/examples/demos/hangman/purchasing/android/src/org/qtproject/qt/android/purchasing/InAppPurchase.java b/examples/demos/hangman/purchasing/android/src/org/qtproject/qt/android/purchasing/InAppPurchase.java
new file mode 100644
index 000000000..1930840a3
--- /dev/null
+++ b/examples/demos/hangman/purchasing/android/src/org/qtproject/qt/android/purchasing/InAppPurchase.java
@@ -0,0 +1,375 @@
+/****************************************************************************
+ **
+ ** Copyright (C) 2021 The Qt Company Ltd.
+ ** Contact: https://www.qt.io/licensing/
+ **
+ ** This file is part of the examples of the Qt Toolkit.
+ **
+ ** $QT_BEGIN_LICENSE:BSD$
+ ** Commercial License Usage
+ ** Licensees holding valid commercial Qt licenses may use this file in
+ ** accordance with the commercial license agreement provided with the
+ ** Software or, alternatively, in accordance with the terms contained in
+ ** a written agreement between you and The Qt Company. For licensing terms
+ ** and conditions see https://www.qt.io/terms-conditions. For further
+ ** information use the contact form at https://www.qt.io/contact-us.
+ **
+ ** BSD License Usage
+ ** Alternatively, you may use this file under the terms of the BSD license
+ ** as follows:
+ **
+ ** "Redistribution and use in source and binary forms, with or without
+ ** modification, are permitted provided that the following conditions are
+ ** met:
+ ** * Redistributions of source code must retain the above copyright
+ ** notice, this list of conditions and the following disclaimer.
+ ** * Redistributions in binary form must reproduce the above copyright
+ ** notice, this list of conditions and the following disclaimer in
+ ** the documentation and/or other materials provided with the
+ ** distribution.
+ ** * Neither the name of The Qt Company Ltd nor the names of its
+ ** contributors may be used to endorse or promote products derived
+ ** from this software without specific prior written permission.
+ **
+ **
+ ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+ **
+ ** $QT_END_LICENSE$
+ **
+ ****************************************************************************/
+
+package org.qtproject.qt.android.purchasing;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.app.Activity;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.billingclient.api.AcknowledgePurchaseParams;
+import com.android.billingclient.api.AcknowledgePurchaseResponseListener;
+import com.android.billingclient.api.BillingClient;
+import com.android.billingclient.api.BillingClientStateListener;
+import com.android.billingclient.api.BillingFlowParams;
+import com.android.billingclient.api.BillingResult;
+import com.android.billingclient.api.ConsumeParams;
+import com.android.billingclient.api.ConsumeResponseListener;
+import com.android.billingclient.api.Purchase;
+import com.android.billingclient.api.Purchase.PurchaseState;
+import com.android.billingclient.api.PurchasesResponseListener;
+import com.android.billingclient.api.PurchasesUpdatedListener;
+import com.android.billingclient.api.SkuDetails;
+import com.android.billingclient.api.SkuDetailsParams;
+import com.android.billingclient.api.SkuDetailsResponseListener;
+
+
+/***********************************************************************
+ ** More info: https://developer.android.com/google/play/billing
+ ** Add Dependencies below to build.gradle file:
+
+dependencies {
+ def billing_version = "4.0.0"
+ implementation "com.android.billingclient:billing:$billing_version"
+}
+
+***********************************************************************/
+
+public class InAppPurchase implements PurchasesUpdatedListener
+{
+ private Context m_context = null;
+ private long m_nativePointer;
+ private String m_publicKey = null;
+ private int purchaseRequestCode;
+
+
+ private BillingClient billingClient;
+
+ public static final int RESULT_OK = BillingClient.BillingResponseCode.OK;
+ public static final int RESULT_USER_CANCELED = BillingClient.BillingResponseCode.USER_CANCELED;
+ public static final String TYPE_INAPP = BillingClient.SkuType.INAPP;
+ public static final String TAG = "InAppPurchase";
+
+ // Should be in sync with InAppTransaction::FailureReason
+ public static final int FAILUREREASON_NOFAILURE = 0;
+ public static final int FAILUREREASON_USERCANCELED = 1;
+ public static final int FAILUREREASON_ERROR = 2;
+
+ public InAppPurchase(Context context, long nativePointer)
+ {
+ m_context = context;
+ m_nativePointer = nativePointer;
+ }
+
+ public void initializeConnection(){
+ billingClient = BillingClient.newBuilder(m_context)
+ .enablePendingPurchases()
+ .setListener(this)
+ .build();
+ billingClient.startConnection(new BillingClientStateListener() {
+ @Override
+ public void onBillingSetupFinished(BillingResult billingResult) {
+ if (billingResult.getResponseCode() == RESULT_OK) {
+ purchasedProductsQueried(m_nativePointer);
+ }
+ }
+
+ @Override
+ public void onBillingServiceDisconnected() {
+ Log.w(TAG, "Billing service disconnected");
+ }
+ });
+ }
+
+ @Override
+ public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
+
+ int responseCode = billingResult.getResponseCode();
+
+ if (purchases == null) {
+ purchaseFailed(purchaseRequestCode, FAILUREREASON_ERROR, "Data missing from result");
+ return;
+ }
+
+ if (billingResult.getResponseCode() == RESULT_OK) {
+ handlePurchase(purchases);
+ } else if (responseCode == RESULT_USER_CANCELED) {
+ purchaseFailed(purchaseRequestCode, FAILUREREASON_USERCANCELED, "");
+ } else {
+ String errorString = getErrorString(responseCode);
+ purchaseFailed(purchaseRequestCode, FAILUREREASON_ERROR, errorString);
+ }
+ }
+
+ //Get list of purchases from onPurchasesUpdated
+ private void handlePurchase(List<Purchase> purchases) {
+
+ for (Purchase purchase : purchases) {
+ try {
+ if (m_publicKey != null && !Security.verifyPurchase(m_publicKey, TYPE_INAPP, purchase.getSignature())) {
+ purchaseFailed(purchaseRequestCode, FAILUREREASON_ERROR, "Signature could not be verified");
+ return;
+ }
+ int purchaseState = purchase.getPurchaseState();
+ if (purchaseState != PurchaseState.PURCHASED) {
+ purchaseFailed(purchaseRequestCode, FAILUREREASON_ERROR, "Unexpected purchase state in result");
+ return;
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ purchaseFailed(purchaseRequestCode, FAILUREREASON_ERROR, e.getMessage());
+ }
+ purchaseSucceeded(purchaseRequestCode, purchase.getSignature(), TYPE_INAPP, purchase.getPurchaseToken(), purchase.getOrderId(), purchase.getPurchaseTime());
+ }
+ }
+
+ public void queryDetails(final String[] productIds) {
+
+ int index = 0;
+ while (index < productIds.length) {
+ List<String> productIdList = new ArrayList<>();
+ for (int i = index; i < Math.min(index + 20, productIds.length); ++i) {
+ productIdList.add(productIds[i]);
+ }
+ index += productIdList.size();
+
+ SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
+ params.setSkusList(productIdList).setType(BillingClient.SkuType.INAPP);
+ billingClient.querySkuDetailsAsync(params.build(),
+ new SkuDetailsResponseListener() {
+ @Override
+ public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
+ int responseCode = billingResult.getResponseCode();
+
+ if (responseCode != RESULT_OK) {
+ Log.e(TAG, "queryDetails: Couldn't retrieve sku details.");
+ return;
+ }
+ if (skuDetailsList == null) {
+ Log.e(TAG, "queryDetails: No details list in response.");
+ return;
+ }
+
+ for (SkuDetails skuDetails : skuDetailsList) {
+ try {
+ String queriedProductId = skuDetails.getSku();
+ String queriedPrice = skuDetails.getPrice();
+ String queriedTitle = skuDetails.getTitle();
+ String queriedDescription = skuDetails.getDescription();
+ registerProduct(m_nativePointer,
+ queriedProductId,
+ queriedPrice,
+ queriedTitle,
+ queriedDescription);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ });
+
+
+ queryPurchasedProducts(productIdList);
+ }
+ }
+
+ //Launch Google purchasing screen
+ public void launchBillingFlow(String identifier, int requestCode){
+
+ purchaseRequestCode = requestCode;
+ List<String> skuList = new ArrayList<>();
+ skuList.add(identifier);
+ SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
+ params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP);
+ billingClient.querySkuDetailsAsync(params.build(),
+ new SkuDetailsResponseListener() {
+ @Override
+ public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
+
+ if (billingResult.getResponseCode() != RESULT_OK) {
+ Log.e(TAG, "Unable to launch Google Play purchase screen");
+ String errorString = getErrorString(requestCode);
+ purchaseFailed(requestCode, FAILUREREASON_ERROR, errorString);
+ return;
+ }
+ else if (skuDetailsList == null){
+ purchaseFailed(purchaseRequestCode, FAILUREREASON_ERROR, "Data missing from result");
+ return;
+ }
+
+ BillingFlowParams purchaseParams = BillingFlowParams.newBuilder()
+ .setSkuDetails(skuDetailsList.get(0))
+ .build();
+
+ //Results will be delivered to onPurchasesUpdated
+ billingClient.launchBillingFlow((Activity) m_context, purchaseParams);
+ }
+ });
+ }
+
+ public void consumePurchase(String purchaseToken){
+
+ ConsumeResponseListener listener = new ConsumeResponseListener() {
+ @Override
+ public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
+ if (billingResult.getResponseCode() != RESULT_OK) {
+ Log.e(TAG, "Unable to consume purchase. Response code: " + billingResult.getResponseCode());
+ }
+ }
+ };
+ ConsumeParams consumeParams =
+ ConsumeParams.newBuilder()
+ .setPurchaseToken(purchaseToken)
+ .build();
+ billingClient.consumeAsync(consumeParams, listener);
+ }
+
+ public void acknowledgeUnlockablePurchase(String purchaseToken){
+
+ AcknowledgePurchaseParams acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
+ .setPurchaseToken(purchaseToken)
+ .build();
+
+ AcknowledgePurchaseResponseListener acknowledgePurchaseResponseListener = new AcknowledgePurchaseResponseListener() {
+ @Override
+ public void onAcknowledgePurchaseResponse(BillingResult billingResult) {
+ if (billingResult.getResponseCode() != RESULT_OK){
+ Log.e(TAG, "Unable to acknowledge purchase. Response code: " + billingResult.getResponseCode());
+ }
+ }
+ };
+ billingClient.acknowledgePurchase(acknowledgePurchaseParams, acknowledgePurchaseResponseListener);
+ }
+
+ public void queryPurchasedProducts(List<String> productIdList) {
+
+ billingClient.queryPurchasesAsync(TYPE_INAPP, new PurchasesResponseListener() {
+ @Override
+ public void onQueryPurchasesResponse(BillingResult billingResult, List<Purchase> list) {
+ for (Purchase purchase : list) {
+
+ if (productIdList.contains(purchase.getSkus().get(0))) {
+ registerPurchased(m_nativePointer,
+ purchase.getSkus().get(0),
+ purchase.getSignature(),
+ TYPE_INAPP,
+ purchase.getPurchaseToken(),
+ purchase.getDeveloperPayload(),
+ purchase.getPurchaseTime());
+ }
+ }
+ }
+ });
+ }
+
+ private String getErrorString(int responseCode){
+ String errorString;
+ switch (responseCode) {
+ case BillingClient.BillingResponseCode.BILLING_UNAVAILABLE: errorString = "Billing unavailable"; break;
+ case BillingClient.BillingResponseCode.ITEM_UNAVAILABLE: errorString = "Item unavailable"; break;
+ case BillingClient.BillingResponseCode.DEVELOPER_ERROR: errorString = "Developer error"; break;
+ case BillingClient.BillingResponseCode.ERROR: errorString = "Fatal error occurred"; break;
+ case BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED: errorString = "Item already owned"; break;
+ case BillingClient.BillingResponseCode.ITEM_NOT_OWNED: errorString = "Item not owned"; break;
+ default: errorString = "Unknown billing error " + responseCode; break;
+ };
+ return errorString;
+ }
+
+ public void setPublicKey(String publicKey)
+ {
+ m_publicKey = publicKey;
+ }
+
+ private void purchaseFailed(int requestCode, int failureReason, String errorString)
+ {
+ purchaseFailed(m_nativePointer, requestCode, failureReason, errorString);
+ }
+
+
+ private void purchaseSucceeded(int requestCode,
+ String signature,
+ String purchaseData,
+ String purchaseToken,
+ String orderId,
+ long timestamp)
+ {
+ purchaseSucceeded(m_nativePointer, requestCode, signature, purchaseData, purchaseToken, orderId, timestamp);
+ }
+
+ private native static void queryFailed(long nativePointer, String productId);
+ private native static void purchasedProductsQueried(long nativePointer);
+ private native static void registerProduct(long nativePointer,
+ String productId,
+ String price,
+ String title,
+ String description);
+ private native static void purchaseFailed(long nativePointer,
+ int requestCode,
+ int failureReason,
+ String errorString);
+ private native static void purchaseSucceeded(long nativePointer,
+ int requestCode,
+ String signature,
+ String data,
+ String purchaseToken,
+ String orderId,
+ long timestamp);
+ private native static void registerPurchased(long nativePointer,
+ String identifier,
+ String signature,
+ String data,
+ String purchaseToken,
+ String orderId,
+ long timestamp);
+}
diff --git a/examples/demos/hangman/purchasing/android/src/org/qtproject/qt/android/purchasing/Security.java b/examples/demos/hangman/purchasing/android/src/org/qtproject/qt/android/purchasing/Security.java
new file mode 100644
index 000000000..5ce0ab681
--- /dev/null
+++ b/examples/demos/hangman/purchasing/android/src/org/qtproject/qt/android/purchasing/Security.java
@@ -0,0 +1,131 @@
+/* Copyright (c) 2012 Google Inc.
+ * Copyright (c) 2015 The Qt Company Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.qtproject.qt.android.purchasing;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+
+/**
+ * Security-related methods. For a secure implementation, all of this code
+ * should be implemented on a server that communicates with the
+ * application on the device. For the sake of simplicity and clarity of this
+ * example, this code is included here and is executed on the device. If you
+ * must verify the purchases on the phone, you should obfuscate this code to
+ * make it harder for an attacker to replace the code with stubs that treat all
+ * purchases as verified.
+ */
+public class Security {
+ private static final String TAG = "IABUtil/Security";
+
+ private static final String KEY_FACTORY_ALGORITHM = "RSA";
+ private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
+
+ /**
+ * Verifies that the data was signed with the given signature, and returns
+ * the verified purchase. The data is in JSON format and signed
+ * with a private key. The data also contains the {@link PurchaseState}
+ * and product ID of the purchase.
+ * @param base64PublicKey the base64-encoded public key to use for verifying.
+ * @param signedData the signed JSON string (signed, not encrypted)
+ * @param signature the signature for the data, signed with the private key
+ */
+ public static boolean verifyPurchase(String base64PublicKey, String signedData, String signature) {
+ if (signedData == null) {
+ Log.e(TAG, "data is null");
+ return false;
+ }
+
+ boolean verified = false;
+ if (!TextUtils.isEmpty(signature)) {
+ PublicKey key = Security.generatePublicKey(base64PublicKey);
+ verified = Security.verify(key, signedData, signature);
+ if (!verified) {
+ Log.w(TAG, "signature does not match data.");
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Generates a PublicKey instance from a string containing the
+ * Base64-encoded public key.
+ *
+ * @param encodedPublicKey Base64-encoded public key
+ * @throws IllegalArgumentException if encodedPublicKey is invalid
+ */
+ public static PublicKey generatePublicKey(String encodedPublicKey) {
+ try {
+ byte[] decodedKey = Base64.decode(encodedPublicKey);
+ KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
+ return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ } catch (InvalidKeySpecException e) {
+ Log.e(TAG, "Invalid key specification.");
+ throw new IllegalArgumentException(e);
+ } catch (Base64DecoderException e) {
+ Log.e(TAG, "Base64 decoding failed.");
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ /**
+ * Verifies that the signature from the server matches the computed
+ * signature on the data. Returns true if the data is correctly signed.
+ *
+ * @param publicKey public key associated with the developer account
+ * @param signedData signed data from server
+ * @param signature server signature
+ * @return true if the data and signature match
+ */
+ public static boolean verify(PublicKey publicKey, String signedData, String signature) {
+ Signature sig;
+ try {
+ sig = Signature.getInstance(SIGNATURE_ALGORITHM);
+ sig.initVerify(publicKey);
+ sig.update(signedData.getBytes());
+ if (!sig.verify(Base64.decode(signature))) {
+ Log.e(TAG, "Signature verification failed.");
+ return false;
+ }
+ return true;
+ } catch (NoSuchAlgorithmException e) {
+ Log.e(TAG, "NoSuchAlgorithmException.");
+ } catch (InvalidKeyException e) {
+ Log.e(TAG, "Invalid key specification.");
+ } catch (SignatureException e) {
+ Log.e(TAG, "Signature exception.");
+ } catch (Base64DecoderException e) {
+ Log.e(TAG, "Base64 decoding failed.");
+ }
+ return false;
+ }
+}
diff --git a/examples/demos/hangman/purchasing/inapp/inappproduct.cpp b/examples/demos/hangman/purchasing/inapp/inappproduct.cpp
new file mode 100644
index 000000000..dc3372f2d
--- /dev/null
+++ b/examples/demos/hangman/purchasing/inapp/inappproduct.cpp
@@ -0,0 +1,107 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "inappproduct.h"
+
+QT_BEGIN_NAMESPACE
+
+class InAppProductPrivate
+{
+public:
+ InAppProductPrivate(const QString &price, const QString &title, const QString &description, InAppProduct::ProductType type, const QString &id)
+ : localPrice(price)
+ , localTitle(title)
+ , localDescription(description)
+ , productType(type)
+ , identifier(id)
+ {
+ }
+
+ QString localPrice;
+ QString localTitle;
+ QString localDescription;
+ InAppProduct::ProductType productType;
+ QString identifier;
+};
+
+InAppProduct::InAppProduct(const QString &price, const QString &title, const QString &description, ProductType productType, const QString &identifier, QObject *parent)
+ : QObject(parent)
+{
+ d = QSharedPointer<InAppProductPrivate>(new InAppProductPrivate(price, title, description, productType, identifier));
+}
+
+InAppProduct::~InAppProduct()
+{
+}
+
+QString InAppProduct::price() const
+{
+ return d->localPrice;
+}
+
+QString InAppProduct::title() const
+{
+ return d->localTitle;
+}
+
+QString InAppProduct::description() const
+{
+ return d->localDescription;
+}
+
+QString InAppProduct::identifier() const
+{
+ return d->identifier;
+}
+
+InAppProduct::ProductType InAppProduct::productType() const
+{
+ return d->productType;
+}
diff --git a/examples/demos/hangman/purchasing/inapp/inappproduct.h b/examples/demos/hangman/purchasing/inapp/inappproduct.h
new file mode 100644
index 000000000..226ca7317
--- /dev/null
+++ b/examples/demos/hangman/purchasing/inapp/inappproduct.h
@@ -0,0 +1,96 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef INAPPPRODUCT_H
+#define INAPPPRODUCT_H
+
+#include <QObject>
+#include <QSharedPointer>
+
+class InAppProductPrivate;
+class InAppProduct: public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QString identifier READ identifier CONSTANT)
+ Q_PROPERTY(ProductType productType READ productType CONSTANT)
+ Q_PROPERTY(QString price READ price CONSTANT)
+ Q_PROPERTY(QString title READ title CONSTANT)
+ Q_PROPERTY(QString description READ description CONSTANT)
+
+public:
+ enum ProductType
+ {
+ Consumable,
+ Unlockable
+ };
+ Q_ENUM(ProductType)
+
+ ~InAppProduct();
+
+ QString identifier() const;
+ ProductType productType() const;
+
+ QString price() const;
+ QString title() const;
+ QString description() const;
+
+ Q_INVOKABLE virtual void purchase() = 0;
+
+protected:
+ explicit InAppProduct(const QString &price, const QString &title, const QString &description, ProductType productType, const QString &identifier, QObject *parent = nullptr);
+
+private:
+ friend class InAppStore;
+ Q_DISABLE_COPY(InAppProduct)
+
+ QSharedPointer<InAppProductPrivate> d;
+};
+
+#endif // INAPPPRODUCT_H
diff --git a/examples/demos/hangman/purchasing/inapp/inapppurchasebackend.cpp b/examples/demos/hangman/purchasing/inapp/inapppurchasebackend.cpp
new file mode 100644
index 000000000..2ebc4678b
--- /dev/null
+++ b/examples/demos/hangman/purchasing/inapp/inapppurchasebackend.cpp
@@ -0,0 +1,92 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "inapppurchasebackend.h"
+
+InAppPurchaseBackend::InAppPurchaseBackend(QObject *parent)
+ : QObject(parent)
+ , m_store(0)
+{
+}
+
+void InAppPurchaseBackend::initialize()
+{
+ emit ready();
+}
+
+bool InAppPurchaseBackend::isReady() const
+{
+ return true;
+}
+
+void InAppPurchaseBackend::queryProducts(const QList<Product> &products)
+{
+ for (const Product &product : products)
+ queryProduct(product.productType, product.identifier);
+}
+
+void InAppPurchaseBackend::queryProduct(InAppProduct::ProductType productType,
+ const QString &identifier)
+{
+ qWarning("InAppPurchaseBackend not implemented on this platform!");
+ Q_UNUSED(productType);
+ Q_UNUSED(identifier);
+}
+
+void InAppPurchaseBackend::restorePurchases()
+{
+ qWarning("InAppPurchaseBackend not implemented on this platform!");
+}
+
+void InAppPurchaseBackend::setPlatformProperty(const QString &propertyName, const QString &value)
+{
+ Q_UNUSED(propertyName);
+ Q_UNUSED(value);
+}
diff --git a/examples/demos/hangman/purchasing/inapp/inapppurchasebackend.h b/examples/demos/hangman/purchasing/inapp/inapppurchasebackend.h
new file mode 100644
index 000000000..49b66f042
--- /dev/null
+++ b/examples/demos/hangman/purchasing/inapp/inapppurchasebackend.h
@@ -0,0 +1,102 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef INAPPPURCHASEBACKEND_H
+#define INAPPPURCHASEBACKEND_H
+
+#include <QObject>
+
+#include "inappproduct.h"
+
+QT_BEGIN_NAMESPACE
+
+class InAppProduct;
+class InAppTransaction;
+class InAppStore;
+class InAppPurchaseBackend : public QObject
+{
+ Q_OBJECT
+public:
+ struct Product
+ {
+ Product(InAppProduct::ProductType type, const QString &id)
+ : productType(type), identifier(id)
+ {
+ }
+
+ InAppProduct::ProductType productType;
+ QString identifier;
+ };
+
+ explicit InAppPurchaseBackend(QObject *parent = 0);
+
+ virtual void initialize();
+ virtual bool isReady() const;
+
+ virtual void queryProducts(const QList<Product> &products);
+ virtual void queryProduct(InAppProduct::ProductType productType, const QString &identifier);
+ virtual void restorePurchases();
+
+ virtual void setPlatformProperty(const QString &propertyName, const QString &value);
+
+ void setStore(InAppStore *store) { m_store = store; }
+ InAppStore *store() const { return m_store; }
+
+Q_SIGNALS:
+ void ready();
+ void transactionReady(InAppTransaction *transaction);
+ void productQueryFailed(InAppProduct::ProductType productType, const QString &identifier);
+ void productQueryDone(InAppProduct *product);
+
+private:
+ InAppStore *m_store;
+};
+
+#endif // INAPPPURCHASEBACKEND_H
diff --git a/examples/demos/hangman/purchasing/inapp/inappstore.cpp b/examples/demos/hangman/purchasing/inapp/inappstore.cpp
new file mode 100644
index 000000000..920572db2
--- /dev/null
+++ b/examples/demos/hangman/purchasing/inapp/inappstore.cpp
@@ -0,0 +1,155 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "inappstore.h"
+#include "inapppurchasebackend.h"
+#include "inapptransaction.h"
+
+#ifdef Q_OS_ANDROID
+#include "../android/androidinapppurchasebackend.h"
+#endif
+
+#ifdef Q_OS_IOS
+#include "../ios/iosinapppurchasebackend.h"
+#endif
+
+class IAPRegisterMetaTypes
+{
+public:
+ IAPRegisterMetaTypes()
+ {
+ qRegisterMetaType<InAppProduct::ProductType>("InAppProduct::ProductType");
+ }
+} _registerIAPMetaTypes;
+
+InAppStore::InAppStore(QObject *parent)
+ : QObject(parent)
+{
+ d = QSharedPointer<InAppStorePrivate>(new InAppStorePrivate);
+ setupBackend();
+}
+
+InAppStore::~InAppStore()
+{
+}
+
+void InAppStore::setupBackend()
+{
+ #ifdef Q_OS_ANDROID
+ d->backend = new AndroidInAppPurchaseBackend;
+ #endif
+ #ifdef Q_OS_IOS
+ d->backend = new IosInAppPurchaseBackend;
+ #endif
+ d->backend->setStore(this);
+
+ connect(d->backend, &InAppPurchaseBackend::ready,
+ this, &InAppStore::registerPendingProducts);
+ connect(d->backend, &InAppPurchaseBackend::transactionReady,
+ this, &InAppStore::transactionReady);
+ connect(d->backend, &InAppPurchaseBackend::productQueryFailed,
+ this, &InAppStore::productUnknown);
+ connect(d->backend, &InAppPurchaseBackend::productQueryDone,
+ this, static_cast<void (InAppStore::*)(InAppProduct *)>(&InAppStore::registerProduct));
+}
+
+void InAppStore::registerProduct(InAppProduct *product)
+{
+ d->registeredProducts[product->identifier()] = product;
+ emit productRegistered(product);
+}
+
+void InAppStore::registerPendingProducts()
+{
+ QList<InAppPurchaseBackend::Product> products;
+ products.reserve(d->pendingProducts.size());
+
+ QHash<QString, InAppProduct::ProductType>::const_iterator it;
+ for (it = d->pendingProducts.constBegin(); it != d->pendingProducts.constEnd(); ++it)
+ products.append(InAppPurchaseBackend::Product(it.value(), it.key()));
+ d->pendingProducts.clear();
+
+ d->backend->queryProducts(products);
+ if (d->pendingRestorePurchases)
+ restorePurchases();
+}
+
+void InAppStore::restorePurchases()
+{
+ if (d->backend->isReady()) {
+ d->pendingRestorePurchases = false;
+ d->backend->restorePurchases();
+ } else {
+ d->pendingRestorePurchases = true;
+ }
+}
+
+void InAppStore::setPlatformProperty(const QString &propertyName, const QString &value)
+{
+ d->backend->setPlatformProperty(propertyName, value);
+}
+
+void InAppStore::registerProduct(InAppProduct::ProductType productType, const QString &identifier)
+{
+ if (!d->backend->isReady()) {
+ d->pendingProducts[identifier] = productType;
+ if (!d->hasCalledInitialize) {
+ d->hasCalledInitialize = true;
+ d->backend->initialize();
+ }
+ } else {
+ d->backend->queryProduct(productType, identifier);
+ }
+}
+
+InAppProduct *InAppStore::registeredProduct(const QString &identifier) const
+{
+ return d->registeredProducts.value(identifier);
+}
diff --git a/examples/demos/hangman/purchasing/inapp/inappstore.h b/examples/demos/hangman/purchasing/inapp/inappstore.h
new file mode 100644
index 000000000..36daa2aa6
--- /dev/null
+++ b/examples/demos/hangman/purchasing/inapp/inappstore.h
@@ -0,0 +1,114 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef INAPPSTORE_H
+#define INAPPSTORE_H
+
+#include <QObject>
+#include <QMutex>
+#include <QHash>
+
+#include "inappproduct.h"
+#include "inapppurchasebackend.h"
+
+class InAppProduct;
+class InAppTransaction;
+class InAppPurchaseBackend;
+class InAppStorePrivate
+{
+public:
+ InAppStorePrivate()
+ : backend(0)
+ , hasCalledInitialize(false)
+ , pendingRestorePurchases(false)
+ {
+ }
+
+ ~InAppStorePrivate()
+ {
+ delete backend;
+ }
+
+ QHash<QString, InAppProduct::ProductType> pendingProducts;
+ QHash<QString, InAppProduct *> registeredProducts;
+ InAppPurchaseBackend *backend;
+ bool hasCalledInitialize;
+ bool pendingRestorePurchases;
+};
+class InAppStore: public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit InAppStore(QObject *parent = nullptr);
+ ~InAppStore();
+
+ Q_INVOKABLE void restorePurchases();
+ Q_INVOKABLE void registerProduct(InAppProduct::ProductType productType, const QString &identifier);
+ Q_INVOKABLE InAppProduct *registeredProduct(const QString &identifier) const;
+ Q_INVOKABLE void setPlatformProperty(const QString &propertyName, const QString &value);
+
+signals:
+ void productRegistered(InAppProduct *product);
+ void productUnknown(InAppProduct::ProductType productType, const QString &identifier);
+ void transactionReady(InAppTransaction *transaction);
+
+private Q_SLOTS:
+ void registerPendingProducts();
+ void registerProduct(InAppProduct *);
+
+private:
+ void setupBackend();
+
+ Q_DISABLE_COPY(InAppStore)
+ QSharedPointer<InAppStorePrivate> d;
+};
+
+#endif // INAPPSTORE_H
diff --git a/examples/demos/hangman/purchasing/inapp/inapptransaction.cpp b/examples/demos/hangman/purchasing/inapp/inapptransaction.cpp
new file mode 100644
index 000000000..0753a3f83
--- /dev/null
+++ b/examples/demos/hangman/purchasing/inapp/inapptransaction.cpp
@@ -0,0 +1,113 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "inapptransaction.h"
+
+class InAppTransactionPrivate
+{
+public:
+ InAppTransactionPrivate(InAppTransaction::TransactionStatus s,
+ InAppProduct *p)
+ : status(s)
+ , product(p)
+ {
+ }
+
+ InAppTransaction::TransactionStatus status;
+ InAppProduct *product;
+};
+
+InAppTransaction::InAppTransaction(TransactionStatus status,
+ InAppProduct *product,
+ QObject *parent)
+ : QObject(parent)
+{
+ d = QSharedPointer<InAppTransactionPrivate>(new InAppTransactionPrivate(status, product));
+}
+
+InAppTransaction::~InAppTransaction()
+{
+}
+
+InAppProduct *InAppTransaction::product() const
+{
+ return d->product;
+}
+
+InAppTransaction::TransactionStatus InAppTransaction::status() const
+{
+ return d->status;
+}
+
+InAppTransaction::FailureReason InAppTransaction::failureReason() const
+{
+ return NoFailure;
+}
+
+QString InAppTransaction::errorString() const
+{
+ return QString();
+}
+
+QDateTime InAppTransaction::timestamp() const
+{
+ return QDateTime();
+}
+
+QString InAppTransaction::orderId() const
+{
+ return QString();
+}
+
+QString InAppTransaction::platformProperty(const QString &propertyName) const
+{
+ Q_UNUSED(propertyName);
+ return QString();
+}
diff --git a/examples/demos/hangman/purchasing/inapp/inapptransaction.h b/examples/demos/hangman/purchasing/inapp/inapptransaction.h
new file mode 100644
index 000000000..30cf334c1
--- /dev/null
+++ b/examples/demos/hangman/purchasing/inapp/inapptransaction.h
@@ -0,0 +1,117 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef INAPPTRANSACTION_H
+#define INAPPTRANSACTION_H
+
+#include <QtCore/qobject.h>
+#include <QtCore/qsharedpointer.h>
+#include <QDateTime>
+#include <QtQml/qqml.h>
+
+#include "inappproduct.h"
+
+QT_BEGIN_NAMESPACE
+
+class InAppProduct;
+class InAppTransactionPrivate;
+class InAppTransaction: public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(TransactionStatus status READ status CONSTANT)
+ Q_PROPERTY(InAppProduct* product READ product CONSTANT)
+ Q_PROPERTY(QString orderId READ orderId CONSTANT)
+ Q_PROPERTY(FailureReason failureReason READ failureReason CONSTANT)
+ Q_PROPERTY(QString errorString READ errorString CONSTANT)
+ Q_PROPERTY(QDateTime timestamp READ timestamp CONSTANT)
+ QML_NAMED_ELEMENT(Transaction)
+ QML_UNAVAILABLE
+
+public:
+ enum TransactionStatus {
+ Unknown,
+ PurchaseApproved,
+ PurchaseFailed,
+ PurchaseRestored
+ };
+ Q_ENUM(TransactionStatus);
+
+ enum FailureReason {
+ NoFailure,
+ CanceledByUser,
+ ErrorOccurred
+ };
+ Q_ENUM(FailureReason);
+
+ ~InAppTransaction();
+
+ InAppProduct *product() const;
+
+ virtual QString orderId() const;
+ virtual FailureReason failureReason() const;
+ virtual QString errorString() const;
+ virtual QDateTime timestamp() const;
+
+ Q_INVOKABLE virtual void finalize() = 0;
+ Q_INVOKABLE virtual QString platformProperty(const QString &propertyName) const;
+
+ TransactionStatus status() const;
+
+protected:
+ explicit InAppTransaction(TransactionStatus status,
+ InAppProduct *product,
+ QObject *parent = nullptr);
+
+private:
+ Q_DISABLE_COPY(InAppTransaction)
+ QSharedPointer<InAppTransactionPrivate> d;
+};
+
+#endif // INAPPTRANSACTION_H
diff --git a/examples/demos/hangman/purchasing/ios/iosinapppurchasebackend.h b/examples/demos/hangman/purchasing/ios/iosinapppurchasebackend.h
new file mode 100644
index 000000000..b89810ff4
--- /dev/null
+++ b/examples/demos/hangman/purchasing/ios/iosinapppurchasebackend.h
@@ -0,0 +1,98 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef IOSINAPPPURCHASEBACKEND_H
+#define IOSINAPPPURCHASEBACKEND_H
+
+#include "../inapp/inapppurchasebackend.h"
+#include "../inapp/inappproduct.h"
+#include "../inapp/inapptransaction.h"
+
+#include <QtCore/QHash>
+
+Q_FORWARD_DECLARE_OBJC_CLASS(QT_MANGLE_NAMESPACE(InAppPurchaseManager));
+
+QT_BEGIN_NAMESPACE
+
+class IosInAppPurchaseProduct;
+class IosInAppPurchaseTransaction;
+
+class IosInAppPurchaseBackend : public InAppPurchaseBackend
+{
+ Q_OBJECT
+public:
+ IosInAppPurchaseBackend(QObject *parent = 0);
+ ~IosInAppPurchaseBackend();
+
+ void initialize();
+ bool isReady() const;
+ void queryProduct(InAppProduct::ProductType productType, const QString &identifier);
+ void restorePurchases();
+ void setPlatformProperty(const QString &propertyName, const QString &value);
+
+ //Called by InAppPurchaseManager
+ Q_INVOKABLE void registerProduct(IosInAppPurchaseProduct *product);
+ Q_INVOKABLE void registerQueryFailure(const QString &productId);
+ Q_INVOKABLE void registerTransaction(IosInAppPurchaseTransaction *transaction);
+ InAppProduct::ProductType productTypeForProductId(const QString &productId);
+ IosInAppPurchaseProduct *registeredProductForProductId(const QString &productId);
+
+private:
+ QT_MANGLE_NAMESPACE(InAppPurchaseManager) *m_iapManager;
+ QHash<QString, InAppProduct::ProductType> m_productTypeForPendingId;
+ QHash<QString, IosInAppPurchaseProduct*> m_registeredProductForId;
+
+private slots:
+ void setParentToBackend(QObject *object);
+};
+
+QT_END_NAMESPACE
+
+#endif // IOSINAPPPURCHASEBACKEND_H
diff --git a/examples/demos/hangman/purchasing/ios/iosinapppurchasebackend.mm b/examples/demos/hangman/purchasing/ios/iosinapppurchasebackend.mm
new file mode 100644
index 000000000..381b87a02
--- /dev/null
+++ b/examples/demos/hangman/purchasing/ios/iosinapppurchasebackend.mm
@@ -0,0 +1,299 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "iosinapppurchasebackend.h"
+#include "iosinapppurchaseproduct.h"
+#include "iosinapppurchasetransaction.h"
+
+#include <QtCore/QString>
+
+#import <StoreKit/StoreKit.h>
+
+@interface QT_MANGLE_NAMESPACE(InAppPurchaseManager) : NSObject <SKProductsRequestDelegate, SKPaymentTransactionObserver>
+{
+ IosInAppPurchaseBackend *backend;
+ NSMutableArray<SKPaymentTransaction *> *pendingTransactions;
+}
+
+-(void)requestProductData:(NSString *)identifier;
+-(void)processPendingTransactions;
+
+@end
+
+@implementation QT_MANGLE_NAMESPACE(InAppPurchaseManager)
+
+-(id)initWithBackend:(IosInAppPurchaseBackend *)iapBackend {
+ if (self = [super init]) {
+ backend = iapBackend;
+ pendingTransactions = [[NSMutableArray<SKPaymentTransaction *> alloc] init];
+ [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
+ qRegisterMetaType<IosInAppPurchaseProduct*>("IosInAppPurchaseProduct*");
+ qRegisterMetaType<IosInAppPurchaseTransaction*>("IosInAppPurchaseTransaction*");
+ }
+ return self;
+}
+
+-(void)dealloc
+{
+ [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
+ [pendingTransactions release];
+ [super dealloc];
+}
+
+-(void)requestProductData:(NSString *)identifier
+{
+ NSSet<NSString *> *productId = [NSSet<NSString *> setWithObject:identifier];
+ SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productId];
+ productsRequest.delegate = self;
+ [productsRequest start];
+}
+
+-(void)processPendingTransactions
+{
+ NSMutableArray<SKPaymentTransaction *> *registeredTransactions = [NSMutableArray<SKPaymentTransaction *> array];
+
+ for (SKPaymentTransaction *transaction in pendingTransactions) {
+ InAppTransaction::TransactionStatus status = [QT_MANGLE_NAMESPACE(InAppPurchaseManager) statusFromTransaction:transaction];
+
+ IosInAppPurchaseProduct *product = backend->registeredProductForProductId(QString::fromNSString(transaction.payment.productIdentifier));
+
+ if (product) {
+ //It is possible that the product doesn't exist yet (because of previous restores).
+ IosInAppPurchaseTransaction *qtTransaction = new IosInAppPurchaseTransaction(transaction, status, product);
+ if (qtTransaction->thread() != backend->thread()) {
+ qtTransaction->moveToThread(backend->thread());
+ QMetaObject::invokeMethod(backend, "setParentToBackend", Qt::AutoConnection, Q_ARG(QObject*, qtTransaction));
+ }
+ [registeredTransactions addObject:transaction];
+ QMetaObject::invokeMethod(backend, "registerTransaction", Qt::AutoConnection, Q_ARG(IosInAppPurchaseTransaction*, qtTransaction));
+ }
+ }
+
+ //Remove registeredTransactions from pendingTransactions
+ [pendingTransactions removeObjectsInArray:registeredTransactions];
+}
+
+
+//SKProductsRequestDelegate
+-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
+{
+ NSArray<SKProduct *> *products = response.products;
+ SKProduct *product = [products count] == 1 ? [[products firstObject] retain] : nil;
+
+ if (product == nil) {
+ //Invalid product ID
+ NSString *invalidId = [response.invalidProductIdentifiers firstObject];
+ QMetaObject::invokeMethod(backend, "registerQueryFailure", Qt::AutoConnection, Q_ARG(QString, QString::fromNSString(invalidId)));
+ } else {
+ //Valid product query
+ //Create a IosInAppPurchaseProduct
+ IosInAppPurchaseProduct *validProduct = new IosInAppPurchaseProduct(product, backend->productTypeForProductId(QString::fromNSString([product productIdentifier])));
+ if (validProduct->thread() != backend->thread()) {
+ validProduct->moveToThread(backend->thread());
+ QMetaObject::invokeMethod(backend, "setParentToBackend", Qt::AutoConnection, Q_ARG(QObject*, validProduct));
+ }
+ QMetaObject::invokeMethod(backend, "registerProduct", Qt::AutoConnection, Q_ARG(IosInAppPurchaseProduct*, validProduct));
+ }
+
+ [request release];
+}
+
++(InAppTransaction::TransactionStatus)statusFromTransaction:(SKPaymentTransaction *)transaction
+{
+ InAppTransaction::TransactionStatus status;
+ switch (transaction.transactionState) {
+ case SKPaymentTransactionStatePurchasing:
+ //Ignore the purchasing state as it's not really a transaction
+ //And its important that it doesn't need to be finalized as
+ //Calling finishTransaction: on a transaction that is
+ //in the SKPaymentTransactionStatePurchasing state throws an exception
+ status = InAppTransaction::Unknown;
+ break;
+ case SKPaymentTransactionStatePurchased:
+ status = InAppTransaction::PurchaseApproved;
+ break;
+ case SKPaymentTransactionStateFailed:
+ status = InAppTransaction::PurchaseFailed;
+ break;
+ case SKPaymentTransactionStateRestored:
+ status = InAppTransaction::PurchaseRestored;
+ break;
+ default:
+ status = InAppTransaction::Unknown;
+ break;
+ }
+ return status;
+}
+
+//SKPaymentTransactionObserver
+- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions
+{
+ Q_UNUSED(queue);
+ for (SKPaymentTransaction *transaction in transactions) {
+ //Create IosInAppPurchaseTransaction
+ InAppTransaction::TransactionStatus status = [QT_MANGLE_NAMESPACE(InAppPurchaseManager) statusFromTransaction:transaction];
+
+ if (status == InAppTransaction::Unknown)
+ continue;
+
+ IosInAppPurchaseProduct *product = backend->registeredProductForProductId(QString::fromNSString(transaction.payment.productIdentifier));
+
+ if (product) {
+ //It is possible that the product doesn't exist yet (because of previous restores).
+ IosInAppPurchaseTransaction *qtTransaction = new IosInAppPurchaseTransaction(transaction, status, product);
+ if (qtTransaction->thread() != backend->thread()) {
+ qtTransaction->moveToThread(backend->thread());
+ QMetaObject::invokeMethod(backend, "setParentToBackend", Qt::AutoConnection, Q_ARG(QObject*, qtTransaction));
+ }
+ QMetaObject::invokeMethod(backend, "registerTransaction", Qt::AutoConnection, Q_ARG(IosInAppPurchaseTransaction*, qtTransaction));
+ } else {
+ //Add the transaction to the pending transactions list
+ [pendingTransactions addObject:transaction];
+ }
+ }
+}
+
+@end
+
+
+QT_BEGIN_NAMESPACE
+
+IosInAppPurchaseBackend::IosInAppPurchaseBackend(QObject *parent)
+ : InAppPurchaseBackend(parent)
+ , m_iapManager(0)
+{
+}
+
+IosInAppPurchaseBackend::~IosInAppPurchaseBackend()
+{
+ [m_iapManager release];
+}
+
+void IosInAppPurchaseBackend::initialize()
+{
+ m_iapManager = [[QT_MANGLE_NAMESPACE(InAppPurchaseManager) alloc] initWithBackend:this];
+ emit InAppPurchaseBackend::ready();
+}
+
+bool IosInAppPurchaseBackend::isReady() const
+{
+ if (m_iapManager)
+ return true;
+ return false;
+}
+
+void IosInAppPurchaseBackend::queryProduct(InAppProduct::ProductType productType, const QString &identifier)
+{
+ Q_UNUSED(productType)
+
+ if (m_productTypeForPendingId.contains(identifier)) {
+ qWarning("Product query already pending for %s", qPrintable(identifier));
+ return;
+ }
+
+ m_productTypeForPendingId[identifier] = productType;
+
+ [m_iapManager requestProductData:(identifier.toNSString())];
+}
+
+void IosInAppPurchaseBackend::restorePurchases()
+{
+ [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
+}
+
+void IosInAppPurchaseBackend::setPlatformProperty(const QString &propertyName, const QString &value)
+{
+ Q_UNUSED(propertyName);
+ Q_UNUSED(value);
+}
+
+void IosInAppPurchaseBackend::registerProduct(IosInAppPurchaseProduct *product)
+{
+ QHash<QString, InAppProduct::ProductType>::iterator it = m_productTypeForPendingId.find(product->identifier());
+ Q_ASSERT(it != m_productTypeForPendingId.end());
+
+ m_registeredProductForId[product->identifier()] = product;
+ emit productQueryDone(product);
+ m_productTypeForPendingId.erase(it);
+ [m_iapManager processPendingTransactions];
+}
+
+void IosInAppPurchaseBackend::registerQueryFailure(const QString &productId)
+{
+ QHash<QString, InAppProduct::ProductType>::iterator it = m_productTypeForPendingId.find(productId);
+ Q_ASSERT(it != m_productTypeForPendingId.end());
+
+ emit InAppPurchaseBackend::productQueryFailed(it.value(), it.key());
+ m_productTypeForPendingId.erase(it);
+}
+
+void IosInAppPurchaseBackend::registerTransaction(IosInAppPurchaseTransaction *transaction)
+{
+ emit InAppPurchaseBackend::transactionReady(transaction);
+}
+
+InAppProduct::ProductType IosInAppPurchaseBackend::productTypeForProductId(const QString &productId)
+{
+ return m_productTypeForPendingId[productId];
+}
+
+IosInAppPurchaseProduct *IosInAppPurchaseBackend::registeredProductForProductId(const QString &productId)
+{
+ return m_registeredProductForId[productId];
+}
+
+void IosInAppPurchaseBackend::setParentToBackend(QObject *object)
+{
+ object->setParent(this);
+}
+
+QT_END_NAMESPACE
+
+#include "moc_iosinapppurchasebackend.cpp"
diff --git a/examples/demos/hangman/purchasing/ios/iosinapppurchaseproduct.h b/examples/demos/hangman/purchasing/ios/iosinapppurchaseproduct.h
new file mode 100644
index 000000000..2cebddd7c
--- /dev/null
+++ b/examples/demos/hangman/purchasing/ios/iosinapppurchaseproduct.h
@@ -0,0 +1,81 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef IOSINAPPPURCHASEPRODUCT_H
+#define IOSINAPPPURCHASEPRODUCT_H
+
+#include "../inapp/inappproduct.h"
+
+#import <StoreKit/StoreKit.h>
+
+@class SKProduct;
+
+QT_BEGIN_NAMESPACE
+
+class IosInAppPurchaseBackend;
+
+class IosInAppPurchaseProduct : public InAppProduct
+{
+ Q_OBJECT
+public:
+ explicit IosInAppPurchaseProduct(SKProduct *product,
+ ProductType productType,
+ IosInAppPurchaseBackend *backend = 0);
+ void purchase();
+
+private:
+ SKProduct *m_nativeProduct;
+};
+
+QT_END_NAMESPACE
+
+Q_DECLARE_METATYPE(IosInAppPurchaseProduct*)
+
+#endif // IOSINAPPPURCHASEPRODUCT_H
diff --git a/examples/demos/hangman/purchasing/ios/iosinapppurchaseproduct.mm b/examples/demos/hangman/purchasing/ios/iosinapppurchaseproduct.mm
new file mode 100644
index 000000000..ac3e46b27
--- /dev/null
+++ b/examples/demos/hangman/purchasing/ios/iosinapppurchaseproduct.mm
@@ -0,0 +1,89 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "iosinapppurchaseproduct.h"
+#include "iosinapppurchasebackend.h"
+
+QT_BEGIN_NAMESPACE
+
+
+static NSString *localizedPrice(SKProduct *product)
+{
+ NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
+ [numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
+ [numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
+ [numberFormatter setLocale:product.priceLocale];
+ NSString *formattedString = [numberFormatter stringFromNumber:product.price];
+ [numberFormatter release];
+ return formattedString;
+}
+
+IosInAppPurchaseProduct::IosInAppPurchaseProduct(SKProduct *product,
+ ProductType productType,
+ IosInAppPurchaseBackend *backend)
+ : InAppProduct(QString::fromNSString(localizedPrice(product)),
+ QString::fromNSString([product localizedTitle]),
+ QString::fromNSString([product localizedDescription]),
+ productType,
+ QString::fromNSString([product productIdentifier]),
+ backend),
+ m_nativeProduct(product)
+{
+}
+
+void IosInAppPurchaseProduct::purchase()
+{
+ SKPayment *payment = [SKPayment paymentWithProduct:m_nativeProduct];
+ [[SKPaymentQueue defaultQueue] addPayment:payment];
+}
+
+QT_END_NAMESPACE
+
+#include "moc_iosinapppurchaseproduct.cpp"
diff --git a/examples/demos/hangman/purchasing/ios/iosinapppurchasetransaction.h b/examples/demos/hangman/purchasing/ios/iosinapppurchasetransaction.h
new file mode 100644
index 000000000..8be014403
--- /dev/null
+++ b/examples/demos/hangman/purchasing/ios/iosinapppurchasetransaction.h
@@ -0,0 +1,92 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef IosINAPPPURCHASETRANSACTION_H
+#define IosINAPPPURCHASETRANSACTION_H
+
+#include "../inapp/inapptransaction.h"
+#include <QtCore/QString>
+
+#import <StoreKit/StoreKit.h>
+#import <Foundation/Foundation.h>
+#import <StoreKit/StoreKitDefines.h>
+
+@class SKPaymentTransaction;
+
+QT_BEGIN_NAMESPACE
+
+class IosInAppPurchaseBackend;
+
+class IosInAppPurchaseTransaction : public InAppTransaction
+{
+ Q_OBJECT
+public:
+ IosInAppPurchaseTransaction(SKPaymentTransaction *transaction,
+ const TransactionStatus status,
+ InAppProduct *product,
+ IosInAppPurchaseBackend *backend = 0);
+
+ void finalize();
+ QString orderId() const;
+ FailureReason failureReason() const;
+ QString errorString() const;
+ QDateTime timestamp() const;
+
+private:
+ SKPaymentTransaction *m_nativeTransaction;
+ QString m_errorString;
+ FailureReason m_failureReason;
+};
+
+QT_END_NAMESPACE
+
+Q_DECLARE_METATYPE(IosInAppPurchaseTransaction*)
+
+#endif // IOSINAPPTRANSACTION_P_H
diff --git a/examples/demos/hangman/purchasing/ios/iosinapppurchasetransaction.mm b/examples/demos/hangman/purchasing/ios/iosinapppurchasetransaction.mm
new file mode 100644
index 000000000..f0bddd5d9
--- /dev/null
+++ b/examples/demos/hangman/purchasing/ios/iosinapppurchasetransaction.mm
@@ -0,0 +1,135 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "iosinapppurchasetransaction.h"
+#include "iosinapppurchasebackend.h"
+
+#import <StoreKit/StoreKit.h>
+
+QT_BEGIN_NAMESPACE
+
+IosInAppPurchaseTransaction::IosInAppPurchaseTransaction(SKPaymentTransaction *transaction,
+ const TransactionStatus status,
+ InAppProduct *product,
+ IosInAppPurchaseBackend *backend)
+ : InAppTransaction(status, product, backend)
+ , m_nativeTransaction(transaction)
+ , m_failureReason(NoFailure)
+{
+ if (status == PurchaseFailed) {
+ m_failureReason = ErrorOccurred;
+ switch (m_nativeTransaction.error.code) {
+ case SKErrorClientInvalid:
+ m_errorString = QStringLiteral("Client Invalid");
+ break;
+ case SKErrorPaymentCancelled:
+ m_errorString = QStringLiteral("Payment Cancelled");
+ m_failureReason = CanceledByUser;
+ break;
+ case SKErrorPaymentInvalid:
+ m_errorString = QStringLiteral("Payment Invalid");
+ break;
+ case SKErrorPaymentNotAllowed:
+ m_errorString = QStringLiteral("Payment Not Allowed");
+ break;
+#if defined(Q_OS_IOS) || defined(Q_OS_TVOS)
+ case SKErrorStoreProductNotAvailable:
+ m_errorString = QStringLiteral("Store Product Not Available");
+ break;
+#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(90300) || QT_TVOS_PLATFORM_SDK_EQUAL_OR_ABOVE(90200)
+ case SKErrorCloudServicePermissionDenied:
+ m_errorString = QStringLiteral("Cloud Service Permission Denied");
+ break;
+ case SKErrorCloudServiceNetworkConnectionFailed:
+ m_errorString = QStringLiteral("Cloud Service Network Connection Failed");
+ break;
+#endif
+ // rdar://35589806
+#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(100300) // || QT_TVOS_PLATFORM_SDK_EQUAL_OR_ABOVE(100200)
+ case SKErrorCloudServiceRevoked:
+ m_errorString = QStringLiteral("Cloud Service Revoked");
+ break;
+#endif
+#endif
+ case SKErrorUnknown:
+ default:
+ m_errorString = QString::fromNSString([m_nativeTransaction.error localizedDescription]);
+ }
+ }
+}
+
+void IosInAppPurchaseTransaction::finalize()
+{
+ [[SKPaymentQueue defaultQueue] finishTransaction:m_nativeTransaction];
+}
+
+QString IosInAppPurchaseTransaction::orderId() const
+{
+ return QString::fromNSString(m_nativeTransaction.transactionIdentifier);
+}
+
+InAppTransaction::FailureReason IosInAppPurchaseTransaction::failureReason() const
+{
+ return m_failureReason;
+}
+
+QString IosInAppPurchaseTransaction::errorString() const
+{
+ return m_errorString;
+}
+
+QDateTime IosInAppPurchaseTransaction::timestamp() const
+{
+ return QDateTime::fromNSDate(m_nativeTransaction.transactionDate);
+}
+
+QT_END_NAMESPACE
+
+#include "moc_iosinapppurchasetransaction.cpp"
diff --git a/examples/demos/hangman/purchasing/purchasing.pri b/examples/demos/hangman/purchasing/purchasing.pri
new file mode 100644
index 000000000..7f71d10e0
--- /dev/null
+++ b/examples/demos/hangman/purchasing/purchasing.pri
@@ -0,0 +1,66 @@
+QT += quick qml quickcontrols2
+
+SOURCES += \
+ $$PWD/qmltypes/inappproductqmltype.cpp \
+ $$PWD/qmltypes/inappstoreqmltype.cpp \
+ $$PWD/inapp/inappproduct.cpp \
+ $$PWD/inapp/inapppurchasebackend.cpp \
+ $$PWD/inapp/inappstore.cpp \
+ $$PWD/inapp/inapptransaction.cpp
+
+HEADERS += \
+ $$PWD/qmltypes/inappproductqmltype.h \
+ $$PWD/qmltypes/inappstoreqmltype.h \
+ $$PWD/inapp/inappproduct.h \
+ $$PWD/inapp/inapppurchasebackend.h \
+ $$PWD/inapp/inappstore.h \
+ $$PWD/inapp/inapptransaction.h
+
+android {
+ QT += core-private
+
+ SOURCES += \
+ $$PWD/android/androidinappproduct.cpp \
+ $$PWD/android/androidinapppurchasebackend.cpp \
+ $$PWD/android/androidinapptransaction.cpp \
+ $$PWD/android/androidjni.cpp
+
+ HEADERS += \
+ $$PWD/android/androidinappproduct.h \
+ $$PWD/android/androidinapppurchasebackend.h \
+ $$PWD/android/androidinapptransaction.h
+
+ ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android/
+ JAVA_CLASS_PATH = $$ANDROID_PACKAGE_SOURCE_DIR/src/org/qtproject/qt/android/purchasing/
+
+ OTHER_FILES += \
+ $$PWD/android/AndroidManifest.xml \
+ $$PWD/android/build.gradle \
+ $$JAVA_CLASS_PATH/Security.java \
+ $$JAVA_CLASS_PATH/InAppPurchase.java \
+ $$JAVA_CLASS_PATH/Base64.java \
+ $$JAVA_CLASS_PATH/Base64DecoderException.java
+}
+
+ios {
+ #Change the following lines to match your unique App ID
+ #to enable in-app purchases on iOS
+ #For example if your App ID is org.qtproject.qt.iosteam.qthangman"
+ #QMAKE_TARGET_BUNDLE_PREFIX = "org.qtproject.qt.iosteam"
+ #TARGET = qthangman
+
+ QMAKE_TARGET_BUNDLE_PREFIX = "org.qtproject.example"
+ TARGET = hangman
+
+ LIBS += -framework StoreKit -framework Foundation
+ MACPATH += $$PWD/mac/
+ HEADERS += \
+ $$PWD/ios/iosinapppurchasebackend.h \
+ $$PWD/ios/iosinapppurchaseproduct.h \
+ $$PWD/ios/iosinapppurchasetransaction.h
+
+ OBJECTIVE_SOURCES += \
+ $$PWD/ios/iosinapppurchasebackend.mm \
+ $$PWD/ios/iosinapppurchaseproduct.mm \
+ $$PWD/ios/iosinapppurchasetransaction.mm
+}
diff --git a/examples/demos/hangman/purchasing/qmltypes/inappproductqmltype.cpp b/examples/demos/hangman/purchasing/qmltypes/inappproductqmltype.cpp
new file mode 100644
index 000000000..e0b13fe73
--- /dev/null
+++ b/examples/demos/hangman/purchasing/qmltypes/inappproductqmltype.cpp
@@ -0,0 +1,260 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtCore/qcoreevent.h>
+
+#include "inappproductqmltype.h"
+#include "inappstoreqmltype.h"
+#include "../inapp/inapptransaction.h"
+#include "../inapp/inappstore.h"
+
+InAppProductQmlType::InAppProductQmlType(QObject *parent)
+ : QObject(parent)
+ , m_status(Uninitialized)
+ , m_type(InAppProductQmlType::ProductType(-1))
+ , m_componentComplete(false)
+ , m_store(0)
+ , m_product(0)
+{
+}
+
+void InAppProductQmlType::setStore(InAppStoreQmlType *store)
+{
+ if (m_store == store)
+ return;
+
+ if (m_store != 0)
+ m_store->store()->disconnect(this);
+
+ m_store = store;
+ connect(m_store->store(), &InAppStore::productRegistered,
+ this, &InAppProductQmlType::handleProductRegistered);
+ connect(m_store->store(), &InAppStore::productUnknown,
+ this, &InAppProductQmlType::handleProductUnknown);
+ connect(m_store->store(), &InAppStore::transactionReady,
+ this, &InAppProductQmlType::handleTransaction);
+
+ updateProduct();
+
+ emit storeChanged();
+}
+
+InAppStoreQmlType *InAppProductQmlType::store() const
+{
+ return m_store;
+}
+
+void InAppProductQmlType::componentComplete()
+{
+ if (!m_componentComplete) {
+ m_componentComplete = true;
+ updateProduct();
+ }
+}
+
+void InAppProductQmlType::setIdentifier(const QString &identifier)
+{
+ if (m_identifier == identifier)
+ return;
+
+ if (m_status != Uninitialized) {
+ qWarning("A product's identifier cannot be changed once the product has been initialized.");
+ return;
+ }
+
+ m_identifier = identifier;
+ if (m_componentComplete)
+ updateProduct();
+ emit identifierChanged();
+}
+
+void InAppProductQmlType::updateProduct()
+{
+ if (m_store == 0)
+ return;
+
+ Status oldStatus = m_status;
+ InAppProduct *product = 0;
+ if (m_identifier.isEmpty() || m_type == InAppProductQmlType::ProductType(-1)) {
+ m_status = Uninitialized;
+ } else {
+ product = m_store->store()->registeredProduct(m_identifier);
+ if (product != 0 && product == m_product)
+ return;
+
+ if (product == 0) {
+ m_status = PendingRegistration;
+ m_store->store()->registerProduct(InAppProduct::ProductType(m_type), m_identifier);
+ } else if (product->productType() != InAppProduct::ProductType(m_type)) {
+ qWarning("Product registered multiple times with different product types.");
+ product = 0;
+ m_status = Uninitialized;
+ } else {
+ m_status = Registered;
+ }
+ }
+
+ setProduct(product);
+ if (oldStatus != m_status)
+ emit statusChanged();
+}
+
+void InAppProductQmlType::resetStatus()
+{
+ updateProduct();
+}
+
+QString InAppProductQmlType::identifier() const
+{
+ return m_identifier;
+}
+
+void InAppProductQmlType::setType(InAppProductQmlType::ProductType type)
+{
+ if (m_type == type)
+ return;
+
+ if (m_status != Uninitialized) {
+ qWarning("A product's type cannot be changed once the product has been initialized.");
+ return;
+ }
+
+ m_type = type;
+ if (m_componentComplete)
+ updateProduct();
+
+ emit typeChanged();
+}
+
+InAppProductQmlType::ProductType InAppProductQmlType::type() const
+{
+ return m_type;
+}
+
+InAppProductQmlType::Status InAppProductQmlType::status() const
+{
+ return m_status;
+}
+
+QString InAppProductQmlType::price() const
+{
+ return m_product != 0 ? m_product->price() : QString();
+}
+
+QString InAppProductQmlType::title() const
+{
+ return m_product != 0 ? m_product->title() : QString();
+}
+
+QString InAppProductQmlType::description() const
+{
+ return m_product != 0 ? m_product->description() : QString();
+}
+
+void InAppProductQmlType::setProduct(InAppProduct *product)
+{
+ if (m_product == product)
+ return;
+
+ QString oldPrice = price();
+ QString oldTitle = title();
+ QString oldDescription = description();
+ m_product = product;
+ if (price() != oldPrice)
+ emit priceChanged();
+ if (title() != oldTitle)
+ emit titleChanged();
+ if (description() != oldDescription)
+ emit descriptionChanged();
+}
+
+void InAppProductQmlType::handleProductRegistered(InAppProduct *product)
+{
+ if (product->identifier() == m_identifier) {
+ Q_ASSERT(product->productType() == InAppProduct::ProductType(m_type));
+ setProduct(product);
+ if (m_status != Registered) {
+ m_status = Registered;
+ emit statusChanged();
+ }
+ }
+}
+
+void InAppProductQmlType::handleProductUnknown(InAppProduct::ProductType, const QString &identifier)
+{
+ if (identifier == m_identifier) {
+ setProduct(0);
+ if (m_status != Unknown) {
+ m_status = Unknown;
+ emit statusChanged();
+ }
+ }
+}
+
+void InAppProductQmlType::handleTransaction(InAppTransaction *transaction)
+{
+ if (transaction->product()->identifier() != m_identifier)
+ return;
+
+ if (transaction->status() == InAppTransaction::PurchaseApproved)
+ emit purchaseSucceeded(transaction);
+ else if (transaction->status() == InAppTransaction::PurchaseRestored)
+ emit purchaseRestored(transaction);
+ else{
+ emit purchaseFailed(transaction);}
+}
+
+void InAppProductQmlType::purchase()
+{
+ if (m_product != 0 && m_status == Registered)
+ m_product->purchase();
+ else
+ qWarning("Attempted to purchase unregistered product");
+}
diff --git a/examples/demos/hangman/purchasing/qmltypes/inappproductqmltype.h b/examples/demos/hangman/purchasing/qmltypes/inappproductqmltype.h
new file mode 100644
index 000000000..9abcfe1ad
--- /dev/null
+++ b/examples/demos/hangman/purchasing/qmltypes/inappproductqmltype.h
@@ -0,0 +1,146 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef INAPPPRODUCTQMLTYPE_H
+#define INAPPPRODUCTQMLTYPE_H
+
+#include <QtQml/qqmlparserstatus.h>
+#include <QObject>
+#include <QtQml/qqml.h>
+
+#include "inappstoreqmltype.h"
+#include "../inapp/inappproduct.h"
+#include "../inapp/inapptransaction.h"
+
+class InAppTransaction;
+class InAppStoreQmlType;
+class InAppProductQmlType : public QObject, public QQmlParserStatus
+{
+ Q_OBJECT
+ Q_INTERFACES(QQmlParserStatus)
+ Q_PROPERTY(QString identifier READ identifier WRITE setIdentifier NOTIFY identifierChanged)
+ Q_PROPERTY(ProductType type READ type WRITE setType NOTIFY typeChanged)
+ Q_PROPERTY(QString price READ price NOTIFY priceChanged)
+ Q_PROPERTY(QString title READ title NOTIFY titleChanged)
+ Q_PROPERTY(QString description READ description NOTIFY descriptionChanged)
+ Q_PROPERTY(Status status READ status NOTIFY statusChanged)
+ Q_PROPERTY(InAppStoreQmlType *store READ store WRITE setStore NOTIFY storeChanged)
+ QML_NAMED_ELEMENT(Product)
+
+public:
+ enum Status {
+ Uninitialized,
+ PendingRegistration,
+ Registered,
+ Unknown
+ };
+ Q_ENUM(Status);
+
+ // Must match InAppProduct::ProductType
+ enum ProductType {
+ Consumable,
+ Unlockable
+ };
+ Q_ENUM(ProductType);
+
+ explicit InAppProductQmlType(QObject *parent = 0);
+
+ Q_INVOKABLE void purchase();
+ Q_INVOKABLE void resetStatus();
+
+ void setIdentifier(const QString &identifier);
+ QString identifier() const;
+
+ Status status() const;
+ QString price() const;
+ QString title() const;
+ QString description() const;
+
+ void setStore(InAppStoreQmlType *store);
+ InAppStoreQmlType *store() const;
+
+ void setType(ProductType type);
+ ProductType type() const;
+
+Q_SIGNALS:
+ void purchaseSucceeded(InAppTransaction *transaction);
+ void purchaseFailed(InAppTransaction *transaction);
+ void purchaseRestored(InAppTransaction *transaction);
+ void identifierChanged();
+ void statusChanged();
+ void priceChanged();
+ void titleChanged();
+ void descriptionChanged();
+ void storeChanged();
+ void typeChanged();
+
+protected:
+ void componentComplete();
+ void classBegin() {}
+
+private Q_SLOTS:
+ void handleTransaction(InAppTransaction *transaction);
+ void handleProductRegistered(InAppProduct *product);
+ void handleProductUnknown(InAppProduct::ProductType, const QString &identifier);
+
+private:
+ void setProduct(InAppProduct *product);
+ void updateProduct();
+
+ QString m_identifier;
+ Status m_status;
+ InAppProductQmlType::ProductType m_type;
+ bool m_componentComplete;
+
+ InAppStoreQmlType *m_store;
+ InAppProduct *m_product;
+};
+
+#endif // INAPPPRODUCTQMLTYPE_H
diff --git a/examples/demos/hangman/purchasing/qmltypes/inappstoreqmltype.cpp b/examples/demos/hangman/purchasing/qmltypes/inappstoreqmltype.cpp
new file mode 100644
index 000000000..4625d9908
--- /dev/null
+++ b/examples/demos/hangman/purchasing/qmltypes/inappstoreqmltype.cpp
@@ -0,0 +1,68 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "inappstoreqmltype.h"
+#include "../inapp/inappstore.h"
+
+InAppStoreQmlType::InAppStoreQmlType(QObject *parent)
+ : QObject(parent)
+ , m_store(new InAppStore(this))
+{
+}
+
+InAppStore *InAppStoreQmlType::store() const
+{
+ return m_store;
+}
+
+void InAppStoreQmlType::restorePurchases()
+{
+ m_store->restorePurchases();
+}
diff --git a/examples/demos/hangman/purchasing/qmltypes/inappstoreqmltype.h b/examples/demos/hangman/purchasing/qmltypes/inappstoreqmltype.h
new file mode 100644
index 000000000..a0fa5e26c
--- /dev/null
+++ b/examples/demos/hangman/purchasing/qmltypes/inappstoreqmltype.h
@@ -0,0 +1,83 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef INAPPSTOREQMLTYPE_H
+#define INAPPSTOREQMLTYPE_H
+
+#include <QList>
+#include <QObject>
+#include <QQmlListProperty>
+#include <QtQml/qqml.h>
+
+#include "inappproductqmltype.h"
+#include "../inapp/inappstore.h"
+
+class InAppStore;
+class InAppProductQmlType;
+class InAppStoreQmlType : public QObject
+{
+ Q_OBJECT
+ QML_NAMED_ELEMENT(Store)
+
+public:
+ explicit InAppStoreQmlType(QObject *parent = 0);
+
+ InAppStore *store() const;
+
+ Q_INVOKABLE void restorePurchases();
+
+private:
+ InAppStore *m_store;
+ QList<InAppProductQmlType *> m_products;
+};
+
+
+
+#endif // INAPPSTOREQMLTYPE_H
diff --git a/examples/demos/hangman/qml/GameView.qml b/examples/demos/hangman/qml/GameView.qml
new file mode 100644
index 000000000..b65974a0f
--- /dev/null
+++ b/examples/demos/hangman/qml/GameView.qml
@@ -0,0 +1,232 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick
+import QtQuick.Controls
+
+Item {
+ id: gameView
+
+ function allContained(owned, word)
+ {
+ for (var i=0; i<word.length; ++i) {
+ if (owned.indexOf(word.charAt(i)) < 0)
+ return false
+ }
+ return true
+ }
+
+ property bool gameOver: applicationData.errorCount > 8
+ property bool success: applicationData.word.length > 0 && !gameOver && allContained(applicationData.lettersOwned, applicationData.word)
+
+ property alias globalButtonHeight: letterSelector.keyHeight
+
+ onGameOverChanged: {
+ if (gameOver)
+ applicationData.gameOverReveal();
+ }
+
+ onSuccessChanged: {
+ if (success === true)
+ applicationData.wordsGuessedCorrectly += 1;
+ }
+
+ Connections {
+ target: applicationData
+ function onWordChanged() {
+ applicationData.wordsGiven += 1;
+ }
+ }
+
+ SimpleButton {
+ id: vowelsAvailableText
+ height: globalButtonHeight
+ width: parent.width * 0.25
+ text: "Vowels: " + applicationData.vowelsAvailable
+ anchors.left: parent.left
+ anchors.verticalCenter: helpButton.verticalCenter
+ anchors.margins: topLevel.globalMargin
+ onClicked: {
+ pageStack.push("StoreView.qml");
+ }
+ }
+
+ ScoreItem {
+ anchors.margins: topLevel.globalMargin
+ anchors.right: helpButton.left
+ anchors.verticalCenter: helpButton.verticalCenter
+ height: globalButtonHeight
+ }
+
+ SimpleButton {
+ id: helpButton
+ anchors.top: parent.top
+ anchors.right: parent.right
+ anchors.margins: topLevel.globalMargin
+ height: globalButtonHeight
+ width: globalButtonHeight
+ text: "?"
+ onClicked: {
+ pageStack.push(Qt.resolvedUrl("HowToView.qml"))
+ }
+ }
+
+ Item {
+ anchors.top: helpButton.bottom
+ anchors.bottom: word.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ Hangman {
+ game: gameView
+ anchors.centerIn: parent
+ width: Math.min(parent.width, parent.height) * 0.75
+ height: width
+ }
+ }
+
+ Word {
+ id: word
+ text: applicationData.word
+ anchors.bottom: letterSelector.top
+ anchors.bottomMargin: parent.height * 0.05
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: parent.width * 0.8
+ height: parent.height * 0.1
+ }
+
+ LetterSelector {
+ id: letterSelector
+ locked: gameOver || success
+ hideVowels: applicationData.vowelsAvailable < 1 && !applicationData.vowelsUnlocked
+ anchors.margins: topLevel.globalMargin
+ anchors.bottom: guessButton.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ height: parent.height * 0.23
+ onLetterSelected: function(letter) {
+ if (applicationData.isVowel(letter) && !applicationData.vowelsUnlocked) {
+ applicationData.vowelsAvailable -= 1;
+ }
+ applicationData.requestLetter(letter.charAt(0));
+ }
+ }
+
+ SimpleButton {
+ id: revealButton
+ anchors.margins: topLevel.globalMargin
+ visible: !(gameOver || success)
+ anchors.bottom: parent.bottom
+ anchors.left: parent.left
+ height: globalButtonHeight
+ width : letterSelector.keyWidth * 3
+ text: "Reveal"
+ onClicked: {
+ applicationData.reveal();
+ }
+ }
+ SimpleButton {
+ id: guessButton
+ anchors.margins: topLevel.globalMargin
+ visible: !(gameOver || success)
+ anchors.bottom: parent.bottom
+ anchors.left: revealButton.right
+ anchors.right: parent.right
+ height: globalButtonHeight
+ text: "Guess"
+ onClicked: {
+ pageStack.push(Qt.resolvedUrl("GuessWordView.qml"));
+ }
+ }
+ SimpleButton {
+ id: resetButton
+ anchors.margins: topLevel.globalMargin
+ visible: gameOver || success
+ anchors.bottom: parent.bottom
+ anchors.left: parent.left
+ anchors.right: parent.right
+ height: globalButtonHeight
+ text: "Play Again?"
+ onClicked: {
+ letterSelector.reset();
+ applicationData.reset();
+ }
+ }
+
+ Text {
+ id: gameOverText
+ visible: gameOver
+ anchors.fill: letterSelector
+ text: "Game Over"
+ fontSizeMode: Text.Fit
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ minimumPointSize: 8
+ font.pointSize: 64
+ color: "white"
+ font.family: "Helvetica"
+ font.weight: Font.Light
+ }
+
+ Text {
+ id: successText
+ visible: success
+ anchors.fill: letterSelector
+ text: "Success"
+ fontSizeMode: Text.Fit
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ minimumPointSize: 8
+ font.pointSize: 64
+ color: "white"
+ font.family: "Helvetica"
+ font.weight: Font.Light
+ }
+}
diff --git a/examples/demos/hangman/qml/GuessWordView.qml b/examples/demos/hangman/qml/GuessWordView.qml
new file mode 100644
index 000000000..b083a7ed4
--- /dev/null
+++ b/examples/demos/hangman/qml/GuessWordView.qml
@@ -0,0 +1,105 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick
+import QtQuick.Controls
+
+Item {
+ id: dialog
+ PageHeader {
+ id: header
+ title: "What is the word?"
+ onClicked: {
+ Qt.inputMethod.hide();
+ }
+ }
+
+ Word {
+ id: word
+ text: applicationData.word
+ anchors.top: header.bottom
+ anchors.bottomMargin: parent.height * 0.05
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: parent.width * 0.8
+ height: parent.height * 0.1
+ }
+
+ function guess() {
+ applicationData.guessWord(input.text)
+ input.text = ""
+ Qt.inputMethod.hide();
+ pageStack.pop();
+ topLevel.forceActiveFocus()
+ }
+
+ TextField {
+ id: input
+ focus: true
+ font.capitalization: Font.AllUppercase
+ inputMethodHints: Qt.ImhLatinOnly | Qt.ImhUppercaseOnly | Qt.ImhNoPredictiveText
+ verticalAlignment: Text.AlignTop
+ horizontalAlignment: Text.AlignHCenter
+ maximumLength: applicationData.word.length
+ anchors.top: word.bottom
+ anchors.right: word.right
+ anchors.left: word.left
+ height: word.height
+ anchors.topMargin: topLevel.globalMargin * 2
+ font.pixelSize: height * 0.65
+ onAccepted: {
+ dialog.guess();
+ }
+ }
+
+ Component.onCompleted: {
+ Qt.inputMethod.show();
+ input.forceActiveFocus();
+ }
+}
diff --git a/examples/demos/hangman/qml/Hangman.qml b/examples/demos/hangman/qml/Hangman.qml
new file mode 100644
index 000000000..ddc957ef6
--- /dev/null
+++ b/examples/demos/hangman/qml/Hangman.qml
@@ -0,0 +1,270 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick
+
+Rectangle {
+ id: hangman
+ color: "transparent"
+
+ property int errorCount: applicationData.errorCount
+ property var game: Item
+
+ Rectangle {
+ id: base
+ anchors.bottom: parent.bottom
+ anchors.left: parent.left
+ anchors.right: parent.right
+ height: parent.height / 20
+
+ opacity: errorCount > 0 ? 1.0 : 0.0
+ Behavior on opacity {
+ NumberAnimation {
+ duration: 300
+ }
+ }
+ }
+
+ Rectangle {
+ id: pole
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ anchors.left: parent.left
+ width: parent.width / 20
+
+ opacity: errorCount > 1 ? 1.0 : 0.0
+ visible: opacity > 0.0
+ Behavior on opacity {
+ NumberAnimation {
+ duration: 300
+ }
+ }
+
+ color: "white"
+ }
+
+ Rectangle {
+ id: horizontalPole
+ anchors.top: pole.top
+ anchors.left: pole.right
+ anchors.right: parent.horizontalCenter
+ anchors.rightMargin: -parent.width / 40
+ height: parent.height / 20
+ color: "white"
+ opacity: errorCount > 2 ? 1.0 : 0.0
+ visible: opacity > 0.0
+ Behavior on opacity {
+ NumberAnimation {
+ duration: 300
+ }
+ }
+ }
+
+ Rectangle {
+ id: rope
+ anchors.top: horizontalPole.bottom
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: parent.width / 20
+ height: parent.height / 5
+ opacity: errorCount > 2 ? 1.0 : 0.0
+ visible: opacity > 0.0
+ Behavior on opacity {
+ NumberAnimation {
+ duration: 300
+ }
+ }
+ color: "white"
+ }
+
+ Item {
+ id: dude
+ anchors.top: rope.bottom
+ anchors.horizontalCenter: rope.horizontalCenter
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: parent.height / 10
+
+ Rectangle {
+ id: head
+ anchors.top: parent.top
+ anchors.topMargin: -head.height * 0.25
+ anchors.horizontalCenter: parent.horizontalCenter
+ height: parent.height * 0.15
+ width: height
+
+ radius: width * 0.5
+ color: "white"
+ opacity: errorCount > 3 ? 1.0 : 0.0
+ visible: opacity > 0.0
+ Behavior on opacity {
+ NumberAnimation {
+ duration: 300
+ }
+ }
+ }
+
+ Rectangle {
+ id: body
+ anchors.top: head.bottom
+ anchors.topMargin: hangman.height / 40
+ anchors.horizontalCenter: head.horizontalCenter
+ height: width * 1.8
+ width: head.width * 1.4
+ opacity: errorCount > 4 ? 1.0 : 0.0
+ visible: opacity > 0.0
+ radius: 5
+ Behavior on opacity {
+ NumberAnimation {
+ duration: 300
+ }
+ }
+ }
+ Rectangle {
+ id: armTop1
+ anchors.top: body.top
+ anchors.right: body.horizontalCenter
+ width: (body.width * 1.85) * 0.5
+ height: head.height * 0.7
+ radius: height
+ opacity: errorCount > 5 ? 1.0 : 0.0
+ visible: opacity > 0.0
+ Behavior on opacity {
+ NumberAnimation {
+ duration: 300
+ }
+ }
+ }
+ Rectangle {
+ id: armTop2
+ anchors.top: body.top
+ anchors.left: body.horizontalCenter
+ width: (body.width * 1.85) * 0.5
+ height: head.height * 0.7
+ radius: height
+ opacity: errorCount > 6 ? 1.0 : 0.0
+ visible: opacity > 0.0
+ Behavior on opacity {
+ NumberAnimation {
+ duration: 300
+ }
+ }
+ }
+
+ Rectangle {
+ id: arm1
+ anchors.top: armTop1.bottom
+ anchors.topMargin: -armTop1.height * 0.5 - radius
+ anchors.left: armTop1.left
+ anchors.bottom: body.bottom
+ radius: width * 0.5
+ width: head.width * 0.35
+ opacity: errorCount > 5 ? 1.0 : 0.0
+ visible: opacity > 0.0
+ Behavior on opacity {
+ NumberAnimation {
+ duration: 300
+ }
+ }
+ }
+
+ Rectangle {
+ id: arm2
+ anchors.top: armTop2.bottom
+ anchors.topMargin: -armTop2.height * 0.5 - radius
+ anchors.right: armTop2.right
+ anchors.bottom: body.bottom
+ radius: width * 0.5
+ width: head.width * 0.35
+ opacity: errorCount > 6 ? 1.0 : 0.0
+ visible: opacity > 0.0
+ Behavior on opacity {
+ NumberAnimation {
+ duration: 300
+ }
+ }
+ }
+
+
+ Rectangle {
+ id: leg1
+ anchors.top: body.bottom
+ anchors.topMargin: -radius
+ anchors.left: body.left
+ height: body.height + radius
+ width: body.width * 0.42857
+ radius: width
+
+ opacity: errorCount > 7 ? 1.0 : 0.0
+ visible: opacity > 0.0
+ Behavior on opacity {
+ NumberAnimation {
+ duration: 300
+ }
+ }
+ }
+
+ Rectangle {
+ id: leg2
+ anchors.top: body.bottom
+ anchors.topMargin: -radius
+ anchors.right: body.right
+ height: body.height + radius
+ width: body.width * 0.42857
+ radius: width
+
+ opacity: errorCount > 8 ? 1.0 : 0.0
+ visible: opacity > 0.0
+ Behavior on opacity {
+ NumberAnimation {
+ duration: 300
+ }
+ }
+ }
+ }
+}
diff --git a/examples/demos/hangman/qml/HowToView.qml b/examples/demos/hangman/qml/HowToView.qml
new file mode 100644
index 000000000..977063cff
--- /dev/null
+++ b/examples/demos/hangman/qml/HowToView.qml
@@ -0,0 +1,153 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick
+
+Item {
+ PageHeader {
+ id: header
+ title: "How to Play"
+ }
+
+ Flickable {
+ id: helpFlickable
+ anchors.top: header.bottom
+ anchors.bottom: parent.bottom
+ anchors.right: parent.right
+ anchors.left: parent.left
+ anchors.margins: topLevel.globalMargin
+ clip: true
+
+ contentHeight: helpContent.height + (topLevel.globalMargin * 2)
+
+ Item {
+ id: helpContent
+ width: parent.width
+ height: contentColumn.height
+ Column {
+ id: contentColumn
+ anchors.top: parent.top
+ anchors.right: parent.right
+ anchors.left: parent.left
+ anchors.margins: topLevel.globalMargin
+ spacing: topLevel.globalMargin
+ Text {
+ height: contentHeight
+ width: parent.width
+ wrapMode: Text.Wrap
+ font.family: "Helvetica"
+ color: "white"
+ font.pixelSize: helpFlickable.height * 0.04
+ text: "\
+Hangman is a classic word game where the objective is to guess a given word \
+before you make too many mistakes and the hangman gets hung.\n"
+ }
+
+ Word {
+ anchors.margins: topLevel.globalMargin
+ height: topLevel.buttonHeight
+ width: parent.width * .8
+ text: "HANGMAN"
+ }
+
+ Text {
+ height: contentHeight
+ width: parent.width
+ wrapMode: Text.Wrap
+ font.family: "Helvetica"
+ color: "white"
+ font.pixelSize: helpFlickable.height * 0.04
+ text: "\
+\nYou play by guessing letters. If you guess a letter that is part of the word \
+it will be shown in any locations in the word it is located. If however it is \
+not part of the word, another piece will be added and the hangman will be one \
+step closer to death. \n"
+ }
+
+ Hangman {
+ height: width
+ width: parent.width / 2
+ errorCount: 9
+ }
+
+ Text {
+ height: contentHeight
+ width: parent.width
+ wrapMode: Text.Wrap
+ font.family: "Helvetica"
+ color: "white"
+ font.pixelSize: helpFlickable.height * 0.04
+ text: "\
+\nVowels must be purchased, unlocked or earned to be used. If you guess a word, \
+any vowels that have not been guess already will be added to your vowel pool."
+ }
+
+ ScoreItem {
+ anchors.margins: topLevel.globalMargin
+ height: topLevel.buttonHeight
+ }
+
+ Text {
+ height: contentHeight
+ width: parent.width
+ wrapMode: Text.Wrap
+ font.family: "Helvetica"
+ color: "white"
+ font.pixelSize: helpFlickable.height * 0.04
+ text: "\
+When you guess a word you are rewarded points. You receive a point for each \
+consonant that was guessed as well as a point for any remaining parts of the \
+hangman. You can not do anything with points, they just show how awesome you are.
+"
+ }
+ }
+ }
+ }
+}
diff --git a/examples/demos/hangman/qml/Key.qml b/examples/demos/hangman/qml/Key.qml
new file mode 100644
index 000000000..6c9c9f929
--- /dev/null
+++ b/examples/demos/hangman/qml/Key.qml
@@ -0,0 +1,66 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick
+
+SimpleButton {
+ id: keyItem
+
+ property bool purchasable: false
+
+ signal keyActivated(string letter)
+
+ onClicked: {
+ if (available) {
+ keyActivated(text);
+ }
+ }
+
+}
diff --git a/examples/demos/hangman/qml/Letter.qml b/examples/demos/hangman/qml/Letter.qml
new file mode 100644
index 000000000..5c9a412b2
--- /dev/null
+++ b/examples/demos/hangman/qml/Letter.qml
@@ -0,0 +1,83 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick
+
+Item {
+ property alias text: label.text
+
+ Text {
+ anchors.top: parent.top
+ anchors.horizontalCenter: parent.horizontalCenter
+ id: label
+ color: "white"
+ font.family: "Helvetica"
+ font.pixelSize: parent.height * 0.75
+
+ opacity: applicationData.lettersOwned.indexOf(text) >= 0 ? 1.0 : 0.0
+ visible: opacity > 0.0
+
+ anchors.horizontalCenterOffset: visible ? 0 : -topLevel.width / 2
+
+ Behavior on anchors.horizontalCenterOffset {
+ NumberAnimation {
+ duration: 500
+ easing.type: Easing.OutQuad
+ }
+ }
+ }
+
+ Rectangle {
+ color: "white"
+ width: parent.width
+ anchors.bottom: parent.bottom
+ anchors.top: label.bottom
+ }
+}
diff --git a/examples/demos/hangman/qml/LetterSelector.qml b/examples/demos/hangman/qml/LetterSelector.qml
new file mode 100644
index 000000000..c2d7f44d4
--- /dev/null
+++ b/examples/demos/hangman/qml/LetterSelector.qml
@@ -0,0 +1,425 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick
+
+Item {
+ id: keyView
+ property real keyWidth: (width - (horizontalSpacing * 9)) / 10
+ property real keyHeight: (height - (verticalSpacing * 2)) / 3
+ property real horizontalSpacing: topLevel.globalMargin / 4
+ property real verticalSpacing: topLevel.globalMargin / 4
+ property bool locked: false
+ property bool hideVowels: false
+
+ Component.onCompleted: topLevel.buttonHeight = keyHeight
+ onKeyHeightChanged: topLevel.buttonHeight = keyHeight
+
+ property var keys: [keyA, keyB, keyC, keyD, keyE, keyF, keyG, keyH, keyI, keyJ,
+ keyH, keyJ, keyK, keyL, keyM, keyN, keyO, keyP, keyQ, keyR, keyS,
+ keyT, keyU, keyV, keyW, keyX, keyY, keyZ];
+
+ function reset() {
+ //Resets all key values to their default state
+ for (var i = 0; i < keys.length; ++i)
+ keys[i].available = true;
+ }
+
+ onLockedChanged: {
+ if (locked) {
+ for (var i = 0; i < keys.length; ++i)
+ keys[i].available = false;
+ }
+ }
+
+ signal letterSelected(string letter)
+ signal guessWordPressed()
+ signal resetPressed()
+ signal revealPressed()
+
+ //Qwerty layout
+ Column {
+ spacing: keyView.verticalSpacing
+ anchors.fill: parent
+ Row {
+ spacing: keyView.horizontalSpacing
+ Key {
+ id: keyQ
+ height: keyView.keyHeight
+ width: keyView.keyWidth
+ text: "Q";
+ available: true
+ purchasable: false
+ onKeyActivated: function(letter) {
+ letterSelected(letter);
+ available = false;
+ }
+ }
+ Key {
+ id: keyW
+ height: keyView.keyHeight
+ width: keyView.keyWidth
+ text: "W";
+ available: true
+ purchasable: false
+ onKeyActivated: function(letter) {
+ letterSelected(letter);
+ available = false;
+ }
+ }
+ Key {
+ id: keyE
+ height: keyView.keyHeight
+ width: keyView.keyWidth
+ text: "E";
+ available: true
+ purchasable: true
+ enabled: !hideVowels
+ opacity: hideVowels ? 0.0 : 1.0
+ onKeyActivated: function(letter) {
+ letterSelected(letter);
+ available = false;
+ }
+ }
+ Key {
+ id: keyR
+ height: keyView.keyHeight
+ width: keyView.keyWidth
+ text: "R";
+ available: true
+ purchasable: false
+ onKeyActivated: function(letter) {
+ letterSelected(letter);
+ available = false;
+ }
+ }
+ Key {
+ id: keyT
+ height: keyView.keyHeight
+ width: keyView.keyWidth
+ text: "T";
+ available: true
+ purchasable: false
+ onKeyActivated: function(letter) {
+ letterSelected(letter);
+ available = false;
+ }
+ }
+ Key {
+ id: keyY
+ height: keyView.keyHeight
+ width: keyView.keyWidth
+ text: "Y";
+ available: true
+ purchasable: false
+ onKeyActivated: function(letter) {
+ letterSelected(letter);
+ available = false;
+ }
+ }
+ Key {
+ id: keyU
+ height: keyView.keyHeight
+ width: keyView.keyWidth
+ text: "U";
+ available: true
+ purchasable: true
+ enabled: !hideVowels
+ opacity: hideVowels ? 0.0 : 1.0
+ onKeyActivated: function(letter) {
+ letterSelected(letter);
+ available = false;
+ }
+ }
+ Key {
+ id: keyI
+ height: keyView.keyHeight
+ width: keyView.keyWidth
+ text: "I";
+ available: true
+ purchasable: true
+ enabled: !hideVowels
+ opacity: hideVowels ? 0.0 : 1.0
+ onKeyActivated: function(letter) {
+ letterSelected(letter);
+ available = false;
+ }
+ }
+ Key {
+ id: keyO
+ height: keyView.keyHeight
+ width: keyView.keyWidth
+ text: "O";
+ available: true
+ purchasable: true
+ enabled: !hideVowels
+ opacity: hideVowels ? 0.0 : 1.0
+ onKeyActivated: function(letter) {
+ letterSelected(letter);
+ available = false;
+ }
+ }
+ Key {
+ id: keyP
+ height: keyView.keyHeight
+ width: keyView.keyWidth
+ text: "P";
+ available: true
+ purchasable: false
+ onKeyActivated: function(letter) {
+ letterSelected(letter);
+ available = false;
+ }
+ }
+ }
+ Row {
+ anchors.horizontalCenter: parent.horizontalCenter
+ spacing: keyView.horizontalSpacing
+ Key {
+ id: keyA
+ height: keyView.keyHeight
+ width: keyView.keyWidth
+ text: "A";
+ available: true
+ purchasable: true
+ enabled: !hideVowels
+ opacity: hideVowels ? 0.0 : 1.0
+ onKeyActivated: function(letter) {
+ letterSelected(letter);
+ available = false;
+ }
+ }
+ Key {
+ id: keyS
+ height: keyView.keyHeight
+ width: keyView.keyWidth
+ text: "S";
+ available: true
+ purchasable: false
+ onKeyActivated: function(letter) {
+ letterSelected(letter);
+ available = false;
+ }
+ }
+ Key {
+ id: keyD
+ height: keyView.keyHeight
+ width: keyView.keyWidth
+ text: "D";
+ available: true
+ purchasable: false
+ onKeyActivated: function(letter) {
+ letterSelected(letter);
+ available = false;
+ }
+ }
+ Key {
+ id: keyF
+ height: keyView.keyHeight
+ width: keyView.keyWidth
+ text: "F";
+ available: true
+ purchasable: false
+ onKeyActivated: function(letter) {
+ letterSelected(letter);
+ available = false;
+ }
+ }
+ Key {
+ id: keyG
+ height: keyView.keyHeight
+ width: keyView.keyWidth
+ text: "G";
+ available: true
+ purchasable: false
+ onKeyActivated: function(letter) {
+ letterSelected(letter);
+ available = false;
+ }
+ }
+ Key {
+ id: keyH
+ height: keyView.keyHeight
+ width: keyView.keyWidth
+ text: "H";
+ available: true
+ purchasable: false
+ onKeyActivated: function(letter) {
+ letterSelected(letter);
+ available = false;
+ }
+ }
+ Key {
+ id: keyJ
+ height: keyView.keyHeight
+ width: keyView.keyWidth
+ text: "J";
+ available: true
+ purchasable: false
+ onKeyActivated: function(letter) {
+ letterSelected(letter);
+ available = false;
+ }
+ }
+ Key {
+ id: keyK
+ height: keyView.keyHeight
+ width: keyView.keyWidth
+ text: "K";
+ available: true
+ purchasable: false
+ onKeyActivated: function(letter) {
+ letterSelected(letter);
+ available = false;
+ }
+ }
+ Key {
+ id: keyL
+ height: keyView.keyHeight
+ width: keyView.keyWidth
+ text: "L";
+ available: true
+ purchasable: false
+ onKeyActivated: function(letter) {
+ letterSelected(letter);
+ available = false;
+ }
+ }
+ }
+ Row {
+ spacing: keyView.horizontalSpacing
+ anchors.horizontalCenter: parent.horizontalCenter
+ Key {
+ id: keyZ
+ height: keyView.keyHeight
+ width: keyView.keyWidth
+ text: "Z";
+ available: true
+ purchasable: false
+ onKeyActivated: function(letter) {
+ letterSelected(letter);
+ available = false;
+ }
+ }
+ Key {
+ id: keyX
+ height: keyView.keyHeight
+ width: keyView.keyWidth
+ text: "X";
+ available: true
+ purchasable: false
+ onKeyActivated: function(letter) {
+ letterSelected(letter);
+ available = false;
+ }
+ }
+ Key {
+ id: keyC
+ height: keyView.keyHeight
+ width: keyView.keyWidth
+ text: "C";
+ available: true
+ purchasable: false
+ onKeyActivated: function(letter) {
+ letterSelected(letter);
+ available = false;
+ }
+ }
+ Key {
+ id: keyV
+ height: keyView.keyHeight
+ width: keyView.keyWidth
+ text: "V";
+ available: true
+ purchasable: false
+ onKeyActivated: function(letter) {
+ letterSelected(letter);
+ available = false;
+ }
+ }
+ Key {
+ id: keyB
+ height: keyView.keyHeight
+ width: keyView.keyWidth
+ text: "B";
+ available: true
+ purchasable: false
+ onKeyActivated: function(letter) {
+ letterSelected(letter);
+ available = false;
+ }
+ }
+ Key {
+ id: keyN
+ height: keyView.keyHeight
+ width: keyView.keyWidth
+ text: "N";
+ available: true
+ purchasable: false
+ onKeyActivated: function(letter) {
+ letterSelected(letter);
+ available = false;
+ }
+ }
+ Key {
+ id: keyM
+ height: keyView.keyHeight
+ width: keyView.keyWidth
+ text: "M";
+ available: true
+ purchasable: false
+ onKeyActivated: function(letter) {
+ letterSelected(letter);
+ available = false;
+ }
+ }
+ }
+ }
+}
diff --git a/examples/demos/hangman/qml/MainView.qml b/examples/demos/hangman/qml/MainView.qml
new file mode 100644
index 000000000..16326acb2
--- /dev/null
+++ b/examples/demos/hangman/qml/MainView.qml
@@ -0,0 +1,93 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick
+import QtQuick.Controls
+import Hangman
+
+Item {
+ id: topLevel
+
+ height: 480
+ width: 320
+ property real buttonHeight: 0
+ property real globalMargin: width * 0.025
+
+ Component.onCompleted: forceActiveFocus()
+ focus: true
+ Keys.onBackPressed: {
+ if (pageStack.depth === 1) {
+ Qt.quit()
+ } else {
+ pageStack.pop()
+ event.accepted = true
+ forceActiveFocus()
+ }
+ }
+
+ Hangman {
+ id: applicationData
+ onWordChanged: {
+ if (word.length > 0)
+ splashLoader.source = "";
+ }
+ }
+
+ StackView {
+ id: pageStack
+ anchors.fill: topLevel
+ initialItem: "GameView.qml"
+ }
+ // ![0]
+ Store {
+ id: iapStore
+ }
+ // ![0]
+}
diff --git a/examples/demos/hangman/qml/PageHeader.qml b/examples/demos/hangman/qml/PageHeader.qml
new file mode 100644
index 000000000..6b0e641b0
--- /dev/null
+++ b/examples/demos/hangman/qml/PageHeader.qml
@@ -0,0 +1,93 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick
+
+Item {
+ id: header
+
+ property alias title: titleText.text
+ property int buttonHeight: topLevel.buttonHeight
+ signal clicked()
+
+
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ height: buttonHeight + (topLevel.globalMargin * 2)
+ SimpleButton {
+ id: backButton
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.margins: topLevel.globalMargin
+ height: buttonHeight
+ width: parent.width * 0.25
+ text: "Back"
+ onClicked: {
+ pageStack.pop();
+ header.clicked();
+ }
+ }
+ Text {
+ id: titleText
+ anchors.left: backButton.right
+ anchors.top: parent.top
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ anchors.margins: topLevel.globalMargin
+ font.family: "Helvetica"
+ color: "white"
+ font.pointSize: 64
+ fontSizeMode: Text.Fit
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ minimumPointSize: 8
+ }
+}
diff --git a/examples/demos/hangman/qml/ScoreItem.qml b/examples/demos/hangman/qml/ScoreItem.qml
new file mode 100644
index 000000000..82ac0420a
--- /dev/null
+++ b/examples/demos/hangman/qml/ScoreItem.qml
@@ -0,0 +1,75 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick
+import QtQuick.Controls
+
+Rectangle {
+ id: borderRect
+ color: "transparent"
+ border.color: "white"
+ width: scoreText.contentWidth + (topLevel.globalMargin * 2)
+ radius: 10
+ Label {
+ id: scoreText
+ anchors.fill: parent
+ anchors.topMargin: topLevel.globalMargin
+ anchors.bottomMargin: topLevel.globalMargin
+ anchors.rightMargin: topLevel.globalMargin
+ anchors.leftMargin: topLevel.globalMargin
+ horizontalAlignment: Text.AlignRight
+ verticalAlignment: Text.AlignVCenter
+ font.pixelSize: parent.height
+ font.family: "Helvetica"
+ font.weight: Font.Light
+ text: applicationData.score
+ color: "white"
+ }
+}
diff --git a/examples/demos/hangman/qml/SimpleButton.qml b/examples/demos/hangman/qml/SimpleButton.qml
new file mode 100644
index 000000000..876d56d20
--- /dev/null
+++ b/examples/demos/hangman/qml/SimpleButton.qml
@@ -0,0 +1,129 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick
+
+Item {
+ id: button
+ property string text: ""
+ property color buttonColor: "white"
+ property color textColor: "black"
+ property bool available: true
+ property alias fontPointSize: buttonText.font.pointSize
+
+ signal clicked()
+
+ state: "NORMAL"
+
+ Rectangle {
+ id: buttonRect
+ anchors.fill: parent
+ radius: 10
+ color: buttonColor
+ visible: button.available
+ Text {
+ id: buttonText
+ anchors.fill: parent
+ anchors.rightMargin: parent.width * 0.05
+ anchors.leftMargin: parent.width * 0.05
+ anchors.bottomMargin: parent.height * 0.20
+ anchors.topMargin: parent.height * 0.20
+ text: button.text
+ color: textColor
+ fontSizeMode: Text.Fit
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ minimumPointSize: 8
+ font.pointSize: 64
+ font.family: "Helvetica"
+ font.weight: Font.Light
+ }
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ button.clicked();
+ }
+ onPressed: {
+ button.state = "PRESSED";
+ }
+ onReleased: {
+ button.state = "NORMAL";
+ }
+ }
+
+ states: [
+ State {
+ name: "NORMAL"
+ PropertyChanges {
+ target: buttonRect
+ color: button.buttonColor
+ border.color: "transparent"
+ }
+ PropertyChanges {
+ target: buttonText
+ color: button.textColor
+ }
+ },
+ State {
+ name: "PRESSED"
+ PropertyChanges {
+ target: buttonRect
+ color: "transparent"
+ border.color: button.buttonColor
+ }
+ PropertyChanges {
+ target: buttonText
+ color: button.buttonColor
+ }
+ }
+ ]
+}
diff --git a/examples/demos/hangman/qml/SplashScreen.qml b/examples/demos/hangman/qml/SplashScreen.qml
new file mode 100644
index 000000000..bd72f68da
--- /dev/null
+++ b/examples/demos/hangman/qml/SplashScreen.qml
@@ -0,0 +1,87 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick
+
+Rectangle {
+ id: topLevel
+ gradient: Gradient {
+ GradientStop {
+ position: 0.0
+ color: "#87E0FD"
+ }
+ GradientStop {
+ position: 0.4
+ color: "#53CBF1"
+ }
+ GradientStop {
+ position: 1.0
+ color: "#05ABE0"
+ }
+ }
+
+ Hangman {
+ id: logo
+ anchors.centerIn: parent
+ height: 268
+ width: 268
+ errorCount: 9
+ }
+
+ Text {
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.top: logo.bottom
+ anchors.topMargin: 10
+ text: "Qt Hangman"
+ font.family: "Helvetica"
+ color: "white"
+ font.pointSize: 24
+ }
+}
diff --git a/examples/demos/hangman/qml/StoreItem.qml b/examples/demos/hangman/qml/StoreItem.qml
new file mode 100644
index 000000000..9f42b9700
--- /dev/null
+++ b/examples/demos/hangman/qml/StoreItem.qml
@@ -0,0 +1,194 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick
+import QtQuick.Controls
+import Hangman
+
+Rectangle {
+ id: storeItem
+
+ property Product product: undefined
+
+ state: "NORMAL"
+
+ visible: product.status == Product.Registered
+ radius: 10
+ color: "white"
+
+ height: titleText.contentHeight + descriptionText.contentHeight + topLevel.globalMargin * 2
+ // ![0]
+ Text {
+ id: titleText
+ text: product.title
+ font.bold: true
+ anchors.right: priceText.left
+ anchors.rightMargin: topLevel.globalMargin
+ anchors.top: parent.top
+ anchors.topMargin: topLevel.globalMargin
+ anchors.left: parent.left
+ anchors.leftMargin: topLevel.globalMargin
+ }
+
+ Text {
+ id: descriptionText
+ text: product.description
+ anchors.right: priceText.left
+ anchors.rightMargin: topLevel.globalMargin
+ anchors.left: parent.left
+ anchors.leftMargin: topLevel.globalMargin
+ anchors.top: titleText.bottom
+ anchors.topMargin: topLevel.globalMargin / 2
+ wrapMode: Text.WordWrap
+ }
+
+ Text {
+ id: priceText
+ text: product.price
+ anchors.right: parent.right
+ anchors.rightMargin: topLevel.globalMargin
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ pendingRect.visible = true;
+ spinBox.visible = true;
+ statusText.text = "Purchasing...";
+ storeItem.state = "PURCHASING";
+ product.purchase();
+ }
+ onPressed: {
+ storeItem.state = "PRESSED";
+ }
+ onReleased: {
+ storeItem.state = "NORMAL";
+ }
+ }
+ // ![0]
+
+ Rectangle {
+ id: pendingRect
+ anchors.fill: parent
+ opacity: 0.0
+ color: "white"
+ radius: parent.radius
+ Text {
+ id: statusText
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ anchors.left: parent.left
+ anchors.right: spinBox.left
+ anchors.margins: topLevel.globalMargin
+ verticalAlignment: Text.AlignVCenter
+ }
+ BusyIndicator {
+ id: spinBox
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ anchors.right: parent.right
+ anchors.margins: topLevel.globalMargin
+ width: height
+ }
+
+ Connections {
+ target: product
+ function onPurchaseSucceeded() {
+ statusText.text = "Purchase Succeeded";
+ spinBox.visible = false;
+
+ }
+ function onPurchaseFailed() {
+ statusText.text = "Purchase Failed";
+ spinBox.visible = false;
+ storeItem.state = "NORMAL";
+ }
+ }
+ }
+
+ states: [
+ State {
+ name: "NORMAL"
+ PropertyChanges {
+ target: storeItem
+ color: "white"
+ border.color: "transparent"
+ }
+ PropertyChanges {
+ target: pendingRect
+ opacity: 0.0
+ }
+ },
+ State {
+ name: "PRESSED"
+ PropertyChanges {
+ target: storeItem
+ color: "transparent"
+ border.color: "white"
+ }
+ },
+ State {
+ name: "PURCHASING"
+ PropertyChanges {
+ target: pendingRect
+ opacity: 1.0
+ }
+ }
+ ]
+
+ transitions: [
+ Transition {
+ from: "PURCHASING"
+ to: "NORMAL"
+ NumberAnimation { target: pendingRect; property: "opacity"; duration: 2000; easing.type: Easing.InExpo }
+ }
+ ]
+}
diff --git a/examples/demos/hangman/qml/StoreView.qml b/examples/demos/hangman/qml/StoreView.qml
new file mode 100644
index 000000000..f022fecbd
--- /dev/null
+++ b/examples/demos/hangman/qml/StoreView.qml
@@ -0,0 +1,150 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Hangman
+
+Item {
+ PageHeader {
+ id: header
+ title: "Hangman Store"
+ }
+
+ Column {
+ anchors.top: header.bottom
+ anchors.bottom: restoreButton.top
+ anchors.margins: topLevel.globalMargin
+ anchors.right: parent.right
+ anchors.left: parent.left
+ spacing: topLevel.globalMargin
+ // ![2]
+ StoreItem {
+ product: product100Vowels
+ width: parent.width
+ }
+
+ StoreItem {
+ product: productUnlockVowels
+ width: parent.width
+ }
+ // ![2]
+ }
+
+ SimpleButton {
+ id: restoreButton
+ anchors.bottom: parent.bottom
+ anchors.margins: topLevel.globalMargin
+ anchors.horizontalCenter: parent.horizontalCenter
+ height: topLevel.buttonHeight
+ width: parent.width * .5
+ text: "Restore Purchases"
+ onClicked: {
+ console.log("restoring...");
+ iapStore.restorePurchases();
+ }
+ }
+
+ // ![0]
+ Product {
+ id: product100Vowels
+ store: iapStore
+ type: Product.Consumable
+ identifier: "qt.io.demo.hangman.100vowels"
+
+ onPurchaseSucceeded: {
+ console.log(identifier + " purchase successful");
+ //Add 100 Vowels
+ applicationData.vowelsAvailable += 100;
+ transaction.finalize();
+ pageStack.pop();
+ }
+
+ onPurchaseFailed: {
+ console.log(identifier + " purchase failed");
+ console.log("reason: "
+ + transaction.failureReason === Transaction.CanceledByUser ? "Canceled" : transaction.errorString);
+ transaction.finalize();
+ }
+ }
+ // ![0]
+ // ![1]
+ Product {
+ id: productUnlockVowels
+ type: Product.Unlockable
+ store: iapStore
+ identifier: "qt.io.demo.hangman.unlockvowels"
+
+ onPurchaseSucceeded: {
+ console.log(identifier + " purchase successful");
+ applicationData.vowelsUnlocked = true;
+ transaction.finalize();
+ pageStack.pop();
+ }
+
+ onPurchaseFailed: {
+ console.log(identifier + " purchase failed");
+ console.log("reason: "
+ + transaction.failureReason === Transaction.CanceledByUser ? "Canceled" : transaction.errorString);
+ transaction.finalize();
+ }
+
+ onPurchaseRestored: {
+ console.log(identifier + " purchase restored");
+ applicationData.vowelsUnlocked = true;
+ console.log("timestamp: " + transaction.timestamp);
+ transaction.finalize();
+ pageStack.pop();
+ }
+ }
+ // ![1]
+
+}
diff --git a/examples/demos/hangman/qml/Word.qml b/examples/demos/hangman/qml/Word.qml
new file mode 100644
index 000000000..12235ea91
--- /dev/null
+++ b/examples/demos/hangman/qml/Word.qml
@@ -0,0 +1,74 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick
+import QtQuick.Controls
+
+Item {
+ id: word
+ property string text: ""
+
+ Row {
+ id: row
+ spacing: topLevel.width / 100
+ anchors.fill: parent
+ Repeater {
+ id: letters
+ model: text.length
+
+ Letter {
+ id: letter
+ text: word.text.charAt(index)
+ width: (word.width / word.text.length) - row.spacing
+ height: word.height
+ }
+ }
+ }
+}
diff --git a/examples/demos/hangman/resources.qrc b/examples/demos/hangman/resources.qrc
new file mode 100644
index 000000000..53257cde6
--- /dev/null
+++ b/examples/demos/hangman/resources.qrc
@@ -0,0 +1,21 @@
+<RCC>
+ <qresource prefix="/">
+ <file>dict.txt</file>
+ <file>main.qml</file>
+ <file>qml/GameView.qml</file>
+ <file>qml/GuessWordView.qml</file>
+ <file>qml/Hangman.qml</file>
+ <file>qml/HowToView.qml</file>
+ <file>qml/Key.qml</file>
+ <file>qml/Letter.qml</file>
+ <file>qml/LetterSelector.qml</file>
+ <file>qml/MainView.qml</file>
+ <file>qml/PageHeader.qml</file>
+ <file>qml/ScoreItem.qml</file>
+ <file>qml/SimpleButton.qml</file>
+ <file>qml/SplashScreen.qml</file>
+ <file>qml/StoreItem.qml</file>
+ <file>qml/StoreView.qml</file>
+ <file>qml/Word.qml</file>
+ </qresource>
+</RCC>