Commit 0a28616e authored by Sharaf Zaman's avatar Sharaf Zaman

Use Google Play Inapp-billing for donations on Android&ChromeOS

# Conflicts:
#	libs/ui/KisWelcomePageWidget.cpp
#	libs/ui/KisWelcomePageWidget.h

(cherry picked from commit 32d7b773)
parent 2dc1c0b9
......@@ -66,6 +66,20 @@
#include <KritaVersionWrapper.h>
#include <KisUsageLogger.h>
#include <kritaversion.h>
#include <QSysInfo>
#include <kis_config.h>
#include <kis_image_config.h>
#include "opengl/kis_opengl.h"
#ifdef Q_OS_ANDROID
#include <QtAndroid>
QPushButton* KisWelcomePageWidget::donationLink;
QLabel* KisWelcomePageWidget::donationBannerImage;
#endif
KisWelcomePageWidget::KisWelcomePageWidget(QWidget *parent)
: QWidget(parent)
......@@ -137,22 +151,35 @@ KisWelcomePageWidget::KisWelcomePageWidget(QWidget *parent)
helpTitleLabel_2->hide();
chkShowNews->hide();
donationLink = new QLabel(dropFrameBorder);
donationLink->setOpenExternalLinks(true);
donationLink->setTextInteractionFlags(Qt::TextBrowserInteraction);
donationLink = new QPushButton(dropFrameBorder);
donationLink->setFlat(true);
QFont f = font();
f.setPointSize(15);
f.setUnderline(true);
donationLink->setFont(f);
connect(donationLink, SIGNAL(clicked(bool)), this, SLOT(slotStartDonationFlow()));
verticalLayout_3->addWidget(donationLink);
verticalLayout_3->setAlignment(donationLink, Qt::AlignTop);
verticalLayout_3->setSpacing(20);
QLabel *donationBannerImage = new QLabel(dropFrameBorder);
donationBannerImage = new QLabel(dropFrameBorder);
QString bannerPath = QStandardPaths::locate(QStandardPaths::AppDataLocation, "share/krita/donation/banner.png");
donationBannerImage->setPixmap(QPixmap(bannerPath));
verticalLayout_3->addWidget(donationBannerImage);
jboolean bannerPurchased = QAndroidJniObject::callStaticMethod<jboolean>("org/krita/android/DonationHelper", "isBadgePurchased", "()Z");
if (bannerPurchased) {
donationLink->hide();
donationBannerImage->show();
QAndroidJniObject::callStaticMethod<void>("org/krita/android/DonationHelper", "endConnection", "()V");
} else {
donationLink->show();
donationBannerImage->hide();
}
#endif
// configure the News area
......@@ -337,8 +364,8 @@ void KisWelcomePageWidget::slotUpdateThemeColors()
#ifdef Q_OS_ANDROID
donationLink->setText(QString("<a style=\"color: " + blendedColor.name() + " \" href=\"https://krita.org/en/support-us/donations?" + analyticsString + "donations" + "\">")
.append(i18n("Krita is free and open source.")).append("<br>").append(i18n("Support Krita's Development!")).append("</a>"));
donationLink->setStyleSheet(blendedStyle);
donationLink->setText(QString(i18n("Get your Krita Supporter Badge here!")));
#endif
// re-populate recent files since they might have themed icons
populateRecentDocuments();
......@@ -402,6 +429,13 @@ void KisWelcomePageWidget::populateRecentDocuments()
#ifdef Q_OS_ANDROID
void KisWelcomePageWidget::slotStartDonationFlow()
{
QAndroidJniObject::callStaticMethod<void>("org/krita/android/DonationHelper", "startBillingFlow", "()V");
}
#endif
void KisWelcomePageWidget::dragEnterEvent(QDragEnterEvent *event)
{
//qDebug() << "dragEnterEvent formats" << event->mimeData()->formats() << "urls" << event->mimeData()->urls() << "has images" << event->mimeData()->hasImage();
......@@ -606,3 +640,13 @@ void KisWelcomePageWidget::updateVersionUpdaterFrame()
}
}
#endif
#ifdef Q_OS_ANDROID
extern "C" JNIEXPORT void JNICALL
Java_org_krita_android_JNIWrappers_donationSuccessful(JNIEnv* /*env*/,
jobject /*obj*/,
jint /*n*/)
{
KisWelcomePageWidget::donationLink->hide();
KisWelcomePageWidget::donationBannerImage->show();
}
#endif
......@@ -59,6 +59,10 @@ public Q_SLOTS:
void slotShowUpdaterErrorDetails();
#endif
#ifdef Q_OS_ANDROID
void slotStartDonationFlow();
#endif
protected:
// QWidget overrides
......@@ -100,7 +104,11 @@ private:
#endif
bool m_checkUpdates {false};
QLabel* donationLink;
#ifdef Q_OS_ANDROID
public:
static QPushButton* donationLink;
static QLabel* donationBannerImage;
#endif
private Q_SLOTS:
void slotNewFileClicked();
......
......@@ -74,5 +74,6 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="com.android.vending.BILLING" />
</manifest>
......@@ -136,3 +136,8 @@ android {
preBuild.dependsOn(copyAssets)
preBuild.dependsOn(removeDuplicateAssets)
}
dependencies {
implementation 'com.android.billingclient:billing:2.2.0'
}
......@@ -8,5 +8,7 @@
<string name="remote_action_swiperight">Swipe Right</string>
<string name="remote_action_circlecw">Circle Clockwise</string>
<string name="remote_action_circleccw">Circle Counter-Clockwise</string>
<string name="something_wrong">Something went wrong...</string>
<string name="cancelled">Cancelled</string>
</resources>
/*
* This file is part of the KDE project
* Copyright (C) 2020 Sharaf Zaman <sharafzaz121@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.krita.android;
import android.util.Log;
import android.widget.Toast;
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.Purchase;
import com.android.billingclient.api.PurchasesUpdatedListener;
import com.android.billingclient.api.SkuDetails;
import com.android.billingclient.api.SkuDetailsParams;
import com.android.billingclient.api.SkuDetailsResponseListener;
import org.krita.R;
import org.qtproject.qt5.android.QtNative;
import java.util.ArrayList;
import java.util.List;
public class DonationHelper implements PurchasesUpdatedListener, BillingClientStateListener, SkuDetailsResponseListener {
private final String LOG_TAG = "krita.DonationHelper";
private BillingClient mBillingClient;
private List<SkuDetails> mSkuDetails;
private static DonationHelper sInstance;
private DonationHelper() {
mBillingClient = BillingClient.newBuilder(QtNative.getContext())
.setListener(this)
.enablePendingPurchases()
.build();
mBillingClient.startConnection(this);
}
public static DonationHelper getInstance() {
if (sInstance == null) {
sInstance = new DonationHelper();
}
return sInstance;
}
@Override
public void onBillingSetupFinished(BillingResult billingResult) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
querySkuDetails();
}
}
private void querySkuDetails() {
List<String> skus = new ArrayList<>();
skus.add("thankyoukiki");
SkuDetailsParams params = SkuDetailsParams.newBuilder()
.setType(BillingClient.SkuType.INAPP)
.setSkusList(skus)
.build();
mBillingClient.querySkuDetailsAsync(params, this);
}
@Override
public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> list) {
if (billingResult == null) {
Log.e(LOG_TAG, "null billingResult");
return;
}
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
if (list != null) {
mSkuDetails = list;
}
} else {
showToast(R.string.something_wrong);
}
}
@Override
public void onBillingServiceDisconnected() {
}
@Override
public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
if (billingResult == null) {
Log.e(LOG_TAG, "null billingResult");
return;
}
switch (billingResult.getResponseCode()) {
case BillingClient.BillingResponseCode.OK:
// only one item, for now
for (Purchase purchase: purchases) {
handlePurchase(purchase);
}
break;
case BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED:
// this shouldn't happen with our current logic!
Log.w(LOG_TAG, "Item already owned");
JNIWrappers.donationSuccessful();
break;
case BillingClient.BillingResponseCode.DEVELOPER_ERROR:
Log.e(LOG_TAG, "Dev Error: " + billingResult.getDebugMessage());
break;
case BillingClient.BillingResponseCode.USER_CANCELED:
showToast(R.string.cancelled);
break;
default:
showToast(R.string.something_wrong);
}
}
private void handlePurchase(Purchase purchase) {
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
ackPurchase(purchase);
JNIWrappers.donationSuccessful();
}
}
private void ackPurchase(Purchase purchase) {
AcknowledgePurchaseParams params = AcknowledgePurchaseParams
.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build();
mBillingClient.acknowledgePurchase(params, new AcknowledgePurchaseResponseListener() {
@Override
public void onAcknowledgePurchaseResponse(BillingResult billingResult) {
Log.d(LOG_TAG, "BillingResult: " + billingResult.getResponseCode());
}
});
}
private static void showToast(final int resourceId) {
QtNative.activity().runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(QtNative.getContext(), resourceId, Toast.LENGTH_LONG).show();
}
});
}
public static void startBillingFlow() {
if (!getInstance().mBillingClient.isReady()) {
getInstance().mBillingClient.startConnection(sInstance);
showToast(R.string.something_wrong);
return;
}
if (getInstance().mSkuDetails != null) {
// there's only one for nwo
for (SkuDetails detail: getInstance().mSkuDetails) {
BillingFlowParams flowParams = BillingFlowParams.newBuilder()
.setSkuDetails(detail)
.build();
getInstance().mBillingClient.launchBillingFlow(QtNative.activity(), flowParams);
}
}
}
// This method will be called from C++ side, to see if the banner has been purchased.
// We only have one item right now, so this will do.
public static boolean isBadgePurchased() {
Purchase.PurchasesResult purchasesResult =
getInstance().mBillingClient.queryPurchases(BillingClient.SkuType.INAPP);
if (purchasesResult.getPurchasesList() != null)
return !purchasesResult.getPurchasesList().isEmpty();
else
return false;
}
public static void endConnection() {
getInstance().mBillingClient.endConnection();
sInstance = null;
}
}
......@@ -22,5 +22,6 @@ package org.krita.android;
class JNIWrappers {
public static native void saveState();
public static native void exitFullScreen();
public static native void donationSuccessful();
}
......@@ -39,6 +39,8 @@ public class MainActivity extends QtActivity {
super.onCreate(savedInstanceState);
new ConfigsManager().handleAssets(this);
DonationHelper.getInstance();
}
@Override
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment