diff --git a/app/src/main/java/com/kabouzeid/gramophone/App.java b/app/src/main/java/com/kabouzeid/gramophone/App.java index 9937185f..d67b3225 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/App.java +++ b/app/src/main/java/com/kabouzeid/gramophone/App.java @@ -2,7 +2,10 @@ package com.kabouzeid.gramophone; import android.app.Application; import android.os.Build; +import android.widget.Toast; +import com.anjlab.android.iab.v3.BillingProcessor; +import com.anjlab.android.iab.v3.TransactionDetails; import com.crashlytics.android.Crashlytics; import com.crashlytics.android.core.CrashlyticsCore; import com.kabouzeid.gramophone.appshortcuts.DynamicShortcutManager; @@ -13,11 +16,19 @@ import io.fabric.sdk.android.Fabric; * @author Karim Abou Zeid (kabouzeid) */ public class App extends Application { + public static final String TAG = App.class.getSimpleName(); + public static final String GOOGLE_PLAY_LICENSE_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjMeADN5Ffnt/ml5SYxNPCn8kGcOYGpHEfNSCts99vVxqmCn6C01E94c17j7rUK2aeHur5uxphZylzopPlQ8P8l1fqty0GPUNRSo18FCJzfGH8HZAwZYOcnRFPaXdaq3InyFJhBiODh2oeAcVK/idH6QraQ4r9HIlzigAg6lgwzxl2wJKDh7X/GMdDntCyzDh8xDQ0wIawFgvgojHwqh2Ci8Gnq6EYRwPA9yHiIIksT8Q30QyM5ewl5QcnWepsls7enNqeHarhpmSibRUDgCsxHoOpny7SyuvZvUI3wuLckDR0ds9hrt614scHHqDOBp/qWCZiAgOPVAEQcURbV09qQIDAQAB"; + public static final String PRO_VERSION_PRODUCT_ID = "pro_version"; + + public static App app; + + private BillingProcessor billingProcessor; @Override public void onCreate() { super.onCreate(); + app = this; // Set up Crashlytics, disabled for debug builds Crashlytics crashlyticsKit = new Crashlytics.Builder() @@ -29,5 +40,35 @@ public class App extends Application { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { new DynamicShortcutManager(this).initDynamicShortcuts(); } + + // automatically restores purchases + billingProcessor = new BillingProcessor(this, App.GOOGLE_PLAY_LICENSE_KEY, new BillingProcessor.IBillingHandler() { + @Override + public void onProductPurchased(String productId, TransactionDetails details) { + } + + @Override + public void onPurchaseHistoryRestored() { + Toast.makeText(App.this, R.string.restored_previous_purchases_please_restart, Toast.LENGTH_LONG).show(); + } + + @Override + public void onBillingError(int errorCode, Throwable error) { + } + + @Override + public void onBillingInitialized() { + } + }); + } + + public static boolean isProVersion() { + return false; //BuildConfig.DEBUG || app.billingProcessor.isPurchased(PRO_VERSION_PRODUCT_ID); + } + + @Override + public void onTerminate() { + super.onTerminate(); + billingProcessor.release(); } } diff --git a/app/src/main/java/com/kabouzeid/gramophone/dialogs/BuyDialog.java b/app/src/main/java/com/kabouzeid/gramophone/dialogs/BuyDialog.java new file mode 100644 index 00000000..982e4366 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/dialogs/BuyDialog.java @@ -0,0 +1,135 @@ +package com.kabouzeid.gramophone.dialogs; + +import android.app.Dialog; +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Build; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.DialogFragment; +import android.util.Log; +import android.widget.Toast; + +import com.afollestad.materialdialogs.DialogAction; +import com.afollestad.materialdialogs.MaterialDialog; +import com.afollestad.materialdialogs.internal.MDButton; +import com.anjlab.android.iab.v3.BillingProcessor; +import com.anjlab.android.iab.v3.SkuDetails; +import com.anjlab.android.iab.v3.TransactionDetails; +import com.kabouzeid.gramophone.App; +import com.kabouzeid.gramophone.BuildConfig; +import com.kabouzeid.gramophone.R; + +import java.lang.ref.WeakReference; + +/** + * @author Karim Abou Zeid (kabouzeid) + */ +public class BuyDialog extends DialogFragment implements BillingProcessor.IBillingHandler { + public static final String TAG = BuyDialog.class.getSimpleName(); + + private BillingProcessor billingProcessor; + + private AsyncTask skuDetailsLoadAsyncTask; + + public static BuyDialog create() { + return new BuyDialog(); + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + billingProcessor = new BillingProcessor(getContext(), App.GOOGLE_PLAY_LICENSE_KEY, this); + return new MaterialDialog.Builder(getContext()) + .title(R.string.buy_pro) + .content("Unlock all features, such as:\n• Folder view\n• All theme colors\n• Black theme\n• Sleep timer") + .positiveText(R.string.buy) + .onPositive(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog materialDialog, @NonNull DialogAction dialogAction) { + billingProcessor.purchase(getActivity(), App.PRO_VERSION_PRODUCT_ID); + } + }) + .build(); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (!billingProcessor.handleActivityResult(requestCode, resultCode, data)) { + super.onActivityResult(requestCode, resultCode, data); + } + } + + @Override + public void onProductPurchased(String productId, TransactionDetails details) { + loadSkuDetails(); + Toast.makeText(getContext(), R.string.thank_you, Toast.LENGTH_SHORT).show(); + } + + @Override + public void onPurchaseHistoryRestored() { + loadSkuDetails(); + Toast.makeText(getContext(), R.string.restored_previous_purchases_please_restart, Toast.LENGTH_LONG).show(); + } + + @Override + public void onBillingError(int errorCode, Throwable error) { + Log.e(TAG, "Billing error: code = " + errorCode, error); + } + + @Override + public void onBillingInitialized() { + loadSkuDetails(); + } + + @Override + public void onDestroy() { + if (billingProcessor != null) { + billingProcessor.release(); + } + if (skuDetailsLoadAsyncTask != null) { + skuDetailsLoadAsyncTask.cancel(true); + } + super.onDestroy(); + } + + private void loadSkuDetails() { + if (skuDetailsLoadAsyncTask != null) { + skuDetailsLoadAsyncTask.cancel(false); + } + skuDetailsLoadAsyncTask = new SkuDetailsLoadAsyncTask(this).execute(); + } + + private static class SkuDetailsLoadAsyncTask extends AsyncTask { + private final WeakReference donationDialogWeakReference; + + public SkuDetailsLoadAsyncTask(BuyDialog donationsDialog) { + this.donationDialogWeakReference = new WeakReference<>(donationsDialog); + } + + @Override + protected SkuDetails doInBackground(Void... params) { + BuyDialog dialog = donationDialogWeakReference.get(); + if (dialog != null) { + return dialog.billingProcessor.getPurchaseListingDetails(App.PRO_VERSION_PRODUCT_ID); + } + cancel(false); + return null; + } + + @Override + protected void onPostExecute(SkuDetails skuDetails) { + super.onPostExecute(skuDetails); + BuyDialog dialog = donationDialogWeakReference.get(); + if (dialog == null) return; + + if (skuDetails == null) { + if (!BuildConfig.DEBUG) dialog.dismiss(); + return; + } + + MDButton positiveButton = ((MaterialDialog) dialog.getDialog()).getActionButton(DialogAction.POSITIVE); + positiveButton.setText(String.format("%s %s", dialog.getString(R.string.buy), skuDetails.priceText)); + } + } +} diff --git a/app/src/main/java/com/kabouzeid/gramophone/dialogs/DonationsDialog.java b/app/src/main/java/com/kabouzeid/gramophone/dialogs/DonationsDialog.java index d9652f60..2fd2e362 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/dialogs/DonationsDialog.java +++ b/app/src/main/java/com/kabouzeid/gramophone/dialogs/DonationsDialog.java @@ -92,7 +92,6 @@ public class DonationsDialog extends DialogFragment implements BillingProcessor. @Override public void onPurchaseHistoryRestored() { loadSkuDetails(); - Toast.makeText(getContext(), R.string.restored_previous_purchases, Toast.LENGTH_SHORT).show(); } @Override diff --git a/app/src/main/java/com/kabouzeid/gramophone/dialogs/SleepTimerDialog.java b/app/src/main/java/com/kabouzeid/gramophone/dialogs/SleepTimerDialog.java index aef5d685..747146d3 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/dialogs/SleepTimerDialog.java +++ b/app/src/main/java/com/kabouzeid/gramophone/dialogs/SleepTimerDialog.java @@ -18,6 +18,7 @@ import android.widget.Toast; import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; import com.afollestad.materialdialogs.internal.ThemeSingleton; +import com.kabouzeid.gramophone.App; import com.kabouzeid.gramophone.R; import com.kabouzeid.gramophone.service.MusicService; import com.kabouzeid.gramophone.util.MusicUtil; @@ -59,6 +60,13 @@ public class SleepTimerDialog extends DialogFragment { if (getActivity() == null) { return; } + if (!App.isProVersion()) { + Toast.makeText(getActivity(), getString(R.string.sleep_timer_is_a_pro_feature), Toast.LENGTH_LONG).show(); + BuyDialog.create().show(getFragmentManager(), "BUY_DIALOG"); + dismiss(); + return; + } + final int minutes = seekArcProgress; PendingIntent pi = makeTimerPendingIntent(PendingIntent.FLAG_CANCEL_CURRENT); diff --git a/app/src/main/java/com/kabouzeid/gramophone/misc/NonProAllowedColors.java b/app/src/main/java/com/kabouzeid/gramophone/misc/NonProAllowedColors.java new file mode 100644 index 00000000..c760a21c --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/misc/NonProAllowedColors.java @@ -0,0 +1,98 @@ +package com.kabouzeid.gramophone.misc; + +import android.graphics.Color; + +/** + * @author Karim Abou Zeid (kabouzeid) + */ + +public interface NonProAllowedColors { + + int[] PRIMARY_COLORS = + new int[]{ + // Red + Color.parseColor("#FFEBEE"), + Color.parseColor("#FFCDD2"), + Color.parseColor("#EF9A9A"), + Color.parseColor("#E57373"), + Color.parseColor("#EF5350"), + Color.parseColor("#F44336"), + Color.parseColor("#E53935"), + Color.parseColor("#D32F2F"), + Color.parseColor("#C62828"), + Color.parseColor("#B71C1C"), + // Pink + Color.parseColor("#FCE4EC"), + Color.parseColor("#F8BBD0"), + Color.parseColor("#F48FB1"), + Color.parseColor("#F06292"), + Color.parseColor("#EC407A"), + Color.parseColor("#E91E63"), + Color.parseColor("#D81B60"), + Color.parseColor("#C2185B"), + Color.parseColor("#AD1457"), + Color.parseColor("#880E4F"), + // Purple + Color.parseColor("#F3E5F5"), + Color.parseColor("#E1BEE7"), + Color.parseColor("#CE93D8"), + Color.parseColor("#BA68C8"), + Color.parseColor("#AB47BC"), + Color.parseColor("#9C27B0"), + Color.parseColor("#8E24AA"), + Color.parseColor("#7B1FA2"), + Color.parseColor("#6A1B9A"), + Color.parseColor("#4A148C"), + // Deep Purple + Color.parseColor("#EDE7F6"), + Color.parseColor("#D1C4E9"), + Color.parseColor("#B39DDB"), + Color.parseColor("#9575CD"), + Color.parseColor("#7E57C2"), + Color.parseColor("#673AB7"), + Color.parseColor("#5E35B1"), + Color.parseColor("#512DA8"), + Color.parseColor("#4527A0"), + Color.parseColor("#311B92"), + // Indigo + Color.parseColor("#E8EAF6"), + Color.parseColor("#C5CAE9"), + Color.parseColor("#9FA8DA"), + Color.parseColor("#7986CB"), + Color.parseColor("#5C6BC0"), + Color.parseColor("#3F51B5"), + Color.parseColor("#3949AB"), + Color.parseColor("#303F9F"), + Color.parseColor("#283593"), + Color.parseColor("#1A237E") + }; + + int[] ACCENT_COLORS = + new int[]{ + // Red + Color.parseColor("#FF8A80"), + Color.parseColor("#FF5252"), + Color.parseColor("#FF1744"), + Color.parseColor("#D50000"), + // Pink + Color.parseColor("#FF80AB"), + Color.parseColor("#FF4081"), + Color.parseColor("#F50057"), + Color.parseColor("#C51162"), + // Purple + Color.parseColor("#EA80FC"), + Color.parseColor("#E040FB"), + Color.parseColor("#D500F9"), + Color.parseColor("#AA00FF"), + // Deep Purple + Color.parseColor("#B388FF"), + Color.parseColor("#7C4DFF"), + Color.parseColor("#651FFF"), + Color.parseColor("#6200EA"), + // Indigo + Color.parseColor("#8C9EFF"), + Color.parseColor("#536DFE"), + Color.parseColor("#3D5AFE"), + Color.parseColor("#304FFE") + }; +} diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/AboutActivity.java b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/AboutActivity.java index 8d9a39ec..f2c69c17 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/AboutActivity.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/AboutActivity.java @@ -15,6 +15,7 @@ import android.widget.TextView; import com.afollestad.materialdialogs.internal.ThemeSingleton; import com.kabouzeid.appthemehelper.ThemeStore; +import com.kabouzeid.gramophone.App; import com.kabouzeid.gramophone.R; import com.kabouzeid.gramophone.dialogs.ChangelogDialog; import com.kabouzeid.gramophone.dialogs.DonationsDialog; @@ -159,11 +160,11 @@ public class AboutActivity extends AbsBaseActivity implements View.OnClickListen private static String getCurrentVersionName(@NonNull final Context context) { try { - return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName; + return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName + (App.isProVersion() ? " Pro" : ""); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } - return "0.0.0"; + return "Unkown"; } @Override diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/MainActivity.java b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/MainActivity.java index c987571c..2959a62f 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/MainActivity.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/MainActivity.java @@ -22,14 +22,16 @@ import android.view.ViewGroup; import android.view.WindowInsets; import android.widget.ImageView; import android.widget.TextView; +import android.widget.Toast; import com.bumptech.glide.Glide; import com.kabouzeid.appthemehelper.ThemeStore; import com.kabouzeid.appthemehelper.util.ATHUtil; import com.kabouzeid.appthemehelper.util.NavigationViewUtil; +import com.kabouzeid.gramophone.App; import com.kabouzeid.gramophone.R; +import com.kabouzeid.gramophone.dialogs.BuyDialog; import com.kabouzeid.gramophone.dialogs.ChangelogDialog; -import com.kabouzeid.gramophone.dialogs.DonationsDialog; import com.kabouzeid.gramophone.glide.SongGlideRequest; import com.kabouzeid.gramophone.helper.MusicPlayerRemote; import com.kabouzeid.gramophone.helper.SearchQueryHelper; @@ -108,6 +110,12 @@ public class MainActivity extends AbsSlidingMusicPanelActivity { } private void setMusicChooser(int key) { + if (!App.isProVersion() && key == FOLDERS) { + Toast.makeText(this, R.string.folder_view_is_a_pro_feature, Toast.LENGTH_LONG).show(); + BuyDialog.create().show(getSupportFragmentManager(), "BUY_DIALOG"); + key = LIBRARY; + } + PreferenceUtil.getInstance(this).setLastMusicChooser(key); switch (key) { case LIBRARY: @@ -160,6 +168,9 @@ public class MainActivity extends AbsSlidingMusicPanelActivity { NavigationViewUtil.setItemIconColors(navigationView, ATHUtil.resolveColor(this, R.attr.iconColor, ThemeStore.textColorSecondary(this)), accentColor); NavigationViewUtil.setItemTextColors(navigationView, ThemeStore.textColorPrimary(this), accentColor); + if (App.isProVersion()) { + navigationView.getMenu().removeGroup(R.id.navigation_drawer_menu_category_buy_pro); + } navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() { @Override public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) { @@ -181,11 +192,11 @@ public class MainActivity extends AbsSlidingMusicPanelActivity { } }, 200); break; - case R.id.support_development: + case R.id.buy_pro: new Handler().postDelayed(new Runnable() { @Override public void run() { - DonationsDialog.create().show(getSupportFragmentManager(), "DONATION_DIALOG"); + BuyDialog.create().show(getSupportFragmentManager(), "BUY_DIALOG"); } }, 200); break; diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/SettingsActivity.java b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/SettingsActivity.java index e59e031b..ddb231cb 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/SettingsActivity.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/SettingsActivity.java @@ -18,15 +18,19 @@ import android.support.v7.preference.TwoStatePreference; import android.support.v7.widget.Toolbar; import android.view.MenuItem; import android.view.View; +import android.widget.Toast; import com.afollestad.materialdialogs.color.ColorChooserDialog; import com.kabouzeid.appthemehelper.ThemeStore; import com.kabouzeid.appthemehelper.common.prefs.supportv7.ATEColorPreference; import com.kabouzeid.appthemehelper.common.prefs.supportv7.ATEPreferenceFragmentCompat; import com.kabouzeid.appthemehelper.util.ColorUtil; +import com.kabouzeid.gramophone.App; import com.kabouzeid.gramophone.R; import com.kabouzeid.gramophone.appshortcuts.DynamicShortcutManager; +import com.kabouzeid.gramophone.dialogs.BuyDialog; import com.kabouzeid.gramophone.helper.MusicPlayerRemote; +import com.kabouzeid.gramophone.misc.NonProAllowedColors; import com.kabouzeid.gramophone.preferences.NowPlayingScreenPreference; import com.kabouzeid.gramophone.preferences.NowPlayingScreenPreferenceDialog; import com.kabouzeid.gramophone.service.MusicService; @@ -34,6 +38,8 @@ import com.kabouzeid.gramophone.ui.activities.base.AbsBaseActivity; import com.kabouzeid.gramophone.util.NavigationUtil; import com.kabouzeid.gramophone.util.PreferenceUtil; +import java.util.Arrays; + import butterknife.BindView; import butterknife.ButterKnife; @@ -71,11 +77,29 @@ public class SettingsActivity extends AbsBaseActivity implements ColorChooserDia public void onColorSelection(@NonNull ColorChooserDialog dialog, @ColorInt int selectedColor) { switch (dialog.getTitle()) { case R.string.primary_color: + if (!App.isProVersion()) { + Arrays.sort(NonProAllowedColors.PRIMARY_COLORS); + if (Arrays.binarySearch(NonProAllowedColors.PRIMARY_COLORS, selectedColor) < 0) { + // color wasn't found + Toast.makeText(this, R.string.only_the_first_5_colors_available, Toast.LENGTH_LONG).show(); + BuyDialog.create().show(getSupportFragmentManager(), "BUY_DIALOG"); + return; + } + } ThemeStore.editTheme(this) .primaryColor(selectedColor) .commit(); break; case R.string.accent_color: + if (!App.isProVersion()) { + Arrays.sort(NonProAllowedColors.ACCENT_COLORS); + if (Arrays.binarySearch(NonProAllowedColors.ACCENT_COLORS, selectedColor) < 0) { + // color wasn't found + Toast.makeText(this, R.string.only_the_first_5_colors_available, Toast.LENGTH_LONG).show(); + BuyDialog.create().show(getSupportFragmentManager(), "BUY_DIALOG"); + return; + } + } ThemeStore.editTheme(this) .accentColor(selectedColor) .commit(); @@ -175,9 +199,17 @@ public class SettingsActivity extends AbsBaseActivity implements ColorChooserDia generalTheme.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, @NonNull Object o) { + String themeName = (String) o; + if (themeName.equals("black") && !App.isProVersion()) { + Toast.makeText(getActivity(), R.string.black_theme_is_a_pro_feature, Toast.LENGTH_LONG).show(); + BuyDialog.create().show(getFragmentManager(), "BUY_DIALOG"); + return false; + } + + int theme = PreferenceUtil.getThemeResFromPrefValue(themeName); setSummary(generalTheme, o); ThemeStore.editTheme(getActivity()) - .activityTheme(PreferenceUtil.getThemeResFromPrefValue((String) o)) + .activityTheme(theme) .commit(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { diff --git a/app/src/main/res/drawable/ic_local_play_white_24dp.xml b/app/src/main/res/drawable/ic_local_play_white_24dp.xml new file mode 100644 index 00000000..a0bab0a1 --- /dev/null +++ b/app/src/main/res/drawable/ic_local_play_white_24dp.xml @@ -0,0 +1,4 @@ + + + + diff --git a/app/src/main/res/menu/menu_drawer.xml b/app/src/main/res/menu/menu_drawer.xml index 980fd874..6d992efc 100644 --- a/app/src/main/res/menu/menu_drawer.xml +++ b/app/src/main/res/menu/menu_drawer.xml @@ -17,10 +17,6 @@ - + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f01b97e0..c130f09d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -222,8 +222,10 @@ Permission to access external storage denied. back Support development + Buy Phonograph Pro Thank you! - Restored previous purchases. + Thanks for purchasing Phonograph Pro! Please restart the app to make use of the new features. + Restored previous purchases. Please restart the app to make use of all features. Play Store illustration by Version Application @@ -249,6 +251,7 @@ For helping me with the design. Website Loading products… + Loading price… Up next Appearance Introduction @@ -303,4 +306,9 @@ Playlist is empty The playing notification provides actions for play/pause etc. Playing notification + Buy + Only the first 5 colors are available in the free version. + The black theme is Phonograph Pro feature. + Sleep timer is a Phonograph Pro feature + Folder view is a Phonograph Pro feature.