diff options
author | Jani Korteniemi <jani.korteniemi@qt.io> | 2021-07-26 13:42:14 +0300 |
---|---|---|
committer | Jani Korteniemi <jani.korteniemi@qt.io> | 2021-11-10 13:46:03 +0200 |
commit | 2eff5e431435f2b5be3511823400080d4029a6c1 (patch) | |
tree | 7d4647e00bdb463bc4bb29b85b445745bc18c0f3 /examples/demos | |
parent | 28dd0b133435e044224c3141265e14f2a2586efc (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')
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 Binary files differnew file mode 100644 index 000000000..df54b8d4e --- /dev/null +++ b/examples/demos/hangman/doc/images/qthangman-example.png diff --git a/examples/demos/hangman/doc/images/qthangman-store-example.png b/examples/demos/hangman/doc/images/qthangman-store-example.png Binary files differnew file mode 100644 index 000000000..99583b520 --- /dev/null +++ b/examples/demos/hangman/doc/images/qthangman-store-example.png 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 ×tamp) +{ + 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 ×tamp) + +{ + 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 ×tamp); + Q_INVOKABLE void purchaseSucceeded(int requestCode, + const QString &signature, + const QString &data, + const QString &purchaseToken, + const QString &orderId, + const QDateTime ×tamp); + 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 ×tamp_) + : 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 ×tamp, + 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 ×tamp, + 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> |