From 9421c5276b9073c775c671850a9b4471a622928e Mon Sep 17 00:00:00 2001 From: Nick Bradbury Date: Fri, 2 Aug 2024 14:44:06 -0400 Subject: [PATCH] Removed the app rating dialog --- .../android/ui/main/WPMainActivity.java | 16 +- .../android/ui/posts/PostsListActivity.kt | 5 +- .../wordpress/android/ui/prefs/AppPrefs.java | 1 - .../android/widgets/AppReviewManager.kt | 260 ++++-------------- 4 files changed, 54 insertions(+), 228 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainActivity.java index 2c730a43acae..582701285601 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainActivity.java @@ -355,7 +355,6 @@ public void onCreate(@Nullable Bundle savedInstanceState) { mIsMagicLinkSignup = getIntent().getBooleanExtra(ARG_IS_MAGIC_LINK_SIGNUP, false); mJetpackConnectSource = (JetpackConnectionSource) getIntent().getSerializableExtra(ARG_JETPACK_CONNECT_SOURCE); String authTokenToSet = null; - boolean canShowAppRatingPrompt = savedInstanceState != null; mBottomNav = findViewById(R.id.bottom_navigation); mBottomNav.init(getSupportFragmentManager(), this, mJetpackFeatureRemovalPhaseHelper); @@ -419,8 +418,6 @@ public void onCreate(@Nullable Bundle savedInstanceState) { && getIntent().getExtras().getBoolean(ARG_CONTINUE_JETPACK_CONNECT, false)) { JetpackConnectionWebViewActivity.startJetpackConnectionFlow(this, NOTIFICATIONS, (SiteModel) getIntent().getSerializableExtra(SITE), mAccountStore.hasAccessToken()); - } else { - canShowAppRatingPrompt = true; } } } else { @@ -469,7 +466,6 @@ && getIntent().getExtras().getBoolean(ARG_CONTINUE_JETPACK_CONNECT, false)) { UpdateTokenPayload payload = new UpdateTokenPayload(authTokenToSet); mDispatcher.dispatch(AccountActionBuilder.newUpdateAccessTokenAction(payload)); } else if (getIntent().getBooleanExtra(ARG_SHOW_LOGIN_EPILOGUE, false) && savedInstanceState == null) { - canShowAppRatingPrompt = false; ActivityLauncher.showLoginEpilogue( this, getIntent().getBooleanExtra(ARG_DO_LOGIN_UPDATE, false), @@ -477,22 +473,18 @@ && getIntent().getExtras().getBoolean(ARG_CONTINUE_JETPACK_CONNECT, false)) { mBuildConfigWrapper.isSiteCreationEnabled() ); } else if (getIntent().getBooleanExtra(ARG_SHOW_SIGNUP_EPILOGUE, false) && savedInstanceState == null) { - canShowAppRatingPrompt = false; ActivityLauncher.showSignupEpilogue(this, getIntent().getStringExtra(SignupEpilogueActivity.EXTRA_SIGNUP_DISPLAY_NAME), getIntent().getStringExtra(SignupEpilogueActivity.EXTRA_SIGNUP_EMAIL_ADDRESS), getIntent().getStringExtra(SignupEpilogueActivity.EXTRA_SIGNUP_PHOTO_URL), getIntent().getStringExtra(SignupEpilogueActivity.EXTRA_SIGNUP_USERNAME), false); } else if (getIntent().getBooleanExtra(ARG_SHOW_SITE_CREATION, false) && savedInstanceState == null) { - canShowAppRatingPrompt = false; ActivityLauncher.newBlogForResult(this, SiteCreationSource.fromString(getIntent().getStringExtra(ARG_SITE_CREATION_SOURCE))); } else if (getIntent().getBooleanExtra(ARG_WP_COM_SIGN_UP, false) && savedInstanceState == null) { - canShowAppRatingPrompt = false; ActivityLauncher.showSignInForResultWpComOnly(this); } else if (getIntent().getBooleanExtra(ARG_BLOGGING_PROMPTS_ONBOARDING, false) && savedInstanceState == null) { - canShowAppRatingPrompt = false; showBloggingPromptsOnboarding(); } @@ -501,10 +493,6 @@ && getIntent().getExtras().getBoolean(ARG_CONTINUE_JETPACK_CONNECT, false)) { mGCMRegistrationScheduler.scheduleRegistration(); } - if (canShowAppRatingPrompt) { - AppReviewManager.INSTANCE.showRateDialogIfNeeded(getSupportFragmentManager()); - } - scheduleLocalNotifications(); initViewModel(); @@ -1184,9 +1172,7 @@ protected void onResume() { getSelectedPage() ); - if (AppReviewManager.INSTANCE.shouldShowInAppReviewsPrompt()) { - AppReviewManager.INSTANCE.launchInAppReviews(this); - } + AppReviewManager.INSTANCE.showInAppReviewsPromptIfNecessary(this); checkForInAppUpdate(); mIsChangingConfiguration = false; diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostsListActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostsListActivity.kt index bbfe0509bfe6..e065f764c6bd 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostsListActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostsListActivity.kt @@ -427,10 +427,9 @@ class PostsListActivity : LocaleAwareActivity(), override fun onResume() { super.onResume() ActivityId.trackLastActivity(ActivityId.POSTS) - if (AppReviewManager.shouldShowInAppReviewsPrompt()) { - AppReviewManager.launchInAppReviews(this) - } + AppReviewManager.showInAppReviewsPromptIfNecessary(this) } + @Suppress("DEPRECATION", "OVERRIDE_DEPRECATION") override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppPrefs.java b/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppPrefs.java index 0d8c30f3d307..df82fd7f5212 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppPrefs.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppPrefs.java @@ -164,7 +164,6 @@ public enum DeletablePrefKey implements PrefKey { PINNED_DYNAMIC_CARD, // PUBLISHED_POST_COUNT will increase until it reaches AppReviewManager.TARGET_COUNT_POST_PUBLISHED PUBLISHED_POST_COUNT, - // PUBLISHED_POST_COUNT will increase until it reaches AppReviewManager.TARGET_COUNT_NOTIFICATIONS IN_APP_REVIEWS_NOTIFICATION_COUNT, BLOGGING_REMINDERS_SHOWN, SHOULD_SCHEDULE_CREATE_SITE_NOTIFICATION, diff --git a/WordPress/src/main/java/org/wordpress/android/widgets/AppReviewManager.kt b/WordPress/src/main/java/org/wordpress/android/widgets/AppReviewManager.kt index 4f9fb0794e66..95275d7baed6 100644 --- a/WordPress/src/main/java/org/wordpress/android/widgets/AppReviewManager.kt +++ b/WordPress/src/main/java/org/wordpress/android/widgets/AppReviewManager.kt @@ -1,20 +1,9 @@ package org.wordpress.android.widgets import android.app.Activity -import android.app.Dialog -import android.content.ActivityNotFoundException import android.content.Context -import android.content.DialogInterface -import android.content.Intent import android.content.SharedPreferences -import android.content.pm.PackageManager -import android.net.Uri -import android.os.Bundle -import androidx.fragment.app.DialogFragment -import androidx.fragment.app.FragmentManager -import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.play.core.review.ReviewManagerFactory -import org.wordpress.android.R import org.wordpress.android.analytics.AnalyticsTracker import org.wordpress.android.models.Note import org.wordpress.android.ui.prefs.AppPrefs @@ -26,13 +15,12 @@ import java.util.concurrent.TimeUnit object AppReviewManager { private const val PREF_NAME = "rate_wpandroid" - private const val KEY_INSTALL_DATE = "rate_install_date" - private const val KEY_LAUNCH_TIMES = "rate_launch_times" - private const val KEY_OPT_OUT = "rate_opt_out" - private const val KEY_ASK_LATER_DATE = "rate_ask_later_date" - private const val KEY_INTERACTIONS = "rate_interactions" + + private const val KEY_RATING_LAUNCH_TIMES = "rate_launch_times" + private const val KEY_RATING_INTERACTIONS = "rate_interactions" + private const val IN_APP_REVIEWS_SHOWN_DATE = "in_app_reviews_shown_date" - private const val DO_NOT_SHOW_IN_APP_REVIEWS_PROMPT = "do_not_show_in_app_reviews_prompt" + private const val TARGET_COUNT_POST_PUBLISHED = 2 private const val TARGET_COUNT_NOTIFICATIONS = 10 @@ -40,19 +28,8 @@ object AppReviewManager { private const val CRITERIA_INSTALL_DAYS: Int = 7 private val criteriaInstallMs = TimeUnit.DAYS.toMillis(CRITERIA_INSTALL_DAYS.toLong()) - // app must have been launched this many times before the rating dialog will appear - private const val CRITERIA_LAUNCH_TIMES: Int = 10 - - // user must have performed this many interactions before the rating dialog will appear - private const val CRITERIA_INTERACTIONS: Int = 10 - - private var installDate = Date() - private var askLaterDate = Date() private var launchTimes = 0 - private var interactions = 0 - private var optOut = false private var inAppReviewsShownDate = Date(0) - private var doNotShowInAppReviewsPrompt = false private lateinit var preferences: SharedPreferences @@ -61,70 +38,18 @@ object AppReviewManager { */ fun init(context: Context) { preferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) - val editor = preferences.edit() - - // If it is the first launch, save the date in shared preference. - if (preferences.getLong(KEY_INSTALL_DATE, 0) == 0L) { - storeInstallDate(context) - } - // Increment launch times - launchTimes = preferences.getInt(KEY_LAUNCH_TIMES, 0) + // Increment launch times - note that like interactions, this was used by our + // previous rating dialog but is no longer used. It is here in case we want + // to include launch times in the future. + launchTimes = preferences.getInt(KEY_RATING_LAUNCH_TIMES, 0) launchTimes++ - editor.putInt(KEY_LAUNCH_TIMES, launchTimes) - editor.apply() - - interactions = preferences.getInt(KEY_INTERACTIONS, 0) - optOut = preferences.getBoolean(KEY_OPT_OUT, false) - installDate = Date(preferences.getLong(KEY_INSTALL_DATE, 0)) - askLaterDate = Date(preferences.getLong(KEY_ASK_LATER_DATE, 0)) - - inAppReviewsShownDate = Date(preferences.getLong(IN_APP_REVIEWS_SHOWN_DATE, 0)) - doNotShowInAppReviewsPrompt = preferences.getBoolean(DO_NOT_SHOW_IN_APP_REVIEWS_PROMPT, false) - } - - fun launchInAppReviews(activity: Activity) { - AppLog.d(T.UTILS, "Launching in-app reviews prompt") - val manager = ReviewManagerFactory.create(activity) - val request = manager.requestReviewFlow() - request.addOnCompleteListener { task -> - if (task.isSuccessful) { - val reviewInfo = task.result - val flow = manager.launchReviewFlow(activity, reviewInfo) - flow.addOnFailureListener { e -> - AppLog.e(T.UTILS, "Error launching google review API flow.", e) - } - } else { - task.logException() - } + preferences.edit().apply { + this.putInt(KEY_RATING_LAUNCH_TIMES, launchTimes) + this.apply() } - resetInAppReviewsCounters() - } - - /** - * Show the rate dialog if the criteria is satisfied. - * @return true if shown, false otherwise. - */ - fun showRateDialogIfNeeded(fragmentManger: FragmentManager): Boolean { - return if (shouldShowRateDialog()) { - showRateDialog(fragmentManger) - true - } else { - false - } - } - - /** - * Called from various places in the app where the user has performed a non-trivial action, such as publishing post - * or page. We use this to avoid showing the rating dialog to uninvolved users - */ - fun incrementInteractions(incrementInteractionTracker: AnalyticsTracker.Stat) { - if (!optOut) { - interactions++ - preferences.edit().putInt(KEY_INTERACTIONS, interactions)?.apply() - AnalyticsTracker.track(incrementInteractionTracker) - } + inAppReviewsShownDate = Date(preferences.getLong(IN_APP_REVIEWS_SHOWN_DATE, 0)) } /** @@ -154,140 +79,43 @@ object AppReviewManager { * Check whether the in-app reviews prompt should be shown or not. * @return true if the prompt should be shown */ - fun shouldShowInAppReviewsPrompt(): Boolean { + private fun shouldShowInAppReviewsPrompt(): Boolean { val shouldWaitAfterLastShown = Date().time - inAppReviewsShownDate.time < criteriaInstallMs - val shouldWaitAfterAskLaterTapped = Date().time - askLaterDate.time < criteriaInstallMs + if (shouldWaitAfterLastShown) { + return false + } + val publishedPostsGoal = AppPrefs.getPublishedPostCount() == TARGET_COUNT_POST_PUBLISHED val notificationsGoal = AppPrefs.getInAppReviewsNotificationCount() == TARGET_COUNT_NOTIFICATIONS - return !doNotShowInAppReviewsPrompt && !shouldWaitAfterAskLaterTapped && !shouldWaitAfterLastShown && - (publishedPostsGoal || notificationsGoal) + return publishedPostsGoal || notificationsGoal } /** - * Check whether the rate dialog should be shown or not. - * @return true if the dialog should be shown + * Show the in-app reviews prompt if the necessary criteria are met */ - private fun shouldShowRateDialog(): Boolean { - return if (optOut or (launchTimes < CRITERIA_LAUNCH_TIMES) or (interactions < CRITERIA_INTERACTIONS)) { - false - } else { - Date().time - installDate.time >= criteriaInstallMs && Date().time - askLaterDate.time >= criteriaInstallMs + fun showInAppReviewsPromptIfNecessary(activity: Activity) { + if (shouldShowInAppReviewsPrompt()) { + launchInAppReviews(activity) } } - private fun showRateDialog(fragmentManger: FragmentManager) { - var dialog = fragmentManger.findFragmentByTag(AppRatingDialog.TAG_APP_RATING_PROMPT_DIALOG) - if (dialog == null) { - dialog = AppRatingDialog() - dialog.show(fragmentManger, AppRatingDialog.TAG_APP_RATING_PROMPT_DIALOG) - AnalyticsTracker.track(AnalyticsTracker.Stat.APP_REVIEWS_SAW_PROMPT) - - resetInAppReviewsCounters() - } - } - - class AppRatingDialog : DialogFragment() { - companion object { - internal const val TAG_APP_RATING_PROMPT_DIALOG = "TAG_APP_RATING_PROMPT_DIALOG" - } - - @Suppress("SwallowedException") - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val builder = MaterialAlertDialogBuilder(requireActivity()) - val appName = getString(R.string.app_name) - val title = getString(R.string.app_rating_title, appName) - builder.setTitle(title) - .setMessage(R.string.app_rating_message) - .setCancelable(true) - .setPositiveButton(R.string.app_rating_rate_now) { _, _ -> - val appPackage = requireActivity().packageName - val url = "market://details?id=$appPackage" - try { - requireActivity().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) - } catch (e: ActivityNotFoundException) { - // play store app isn't on this device so open app's page in browser instead - requireActivity().startActivity( - Intent( - Intent.ACTION_VIEW, - Uri.parse( - "http://play.google.com/store/apps/details?id=" + - requireActivity().packageName - ) - ) - ) - } - - setOptOut() - AnalyticsTracker.track(AnalyticsTracker.Stat.APP_REVIEWS_RATED_APP) - - // Reset the published post counter of in-app reviews prompt flow. - AppPrefs.resetPublishedPostCount() - } - .setNeutralButton(R.string.app_rating_rate_later) { _, _ -> - clearSharedPreferences() - storeAskLaterDate() - AnalyticsTracker.track(AnalyticsTracker.Stat.APP_REVIEWS_DECIDED_TO_RATE_LATER) - } - .setNegativeButton(R.string.app_rating_rate_never) { _, _ -> - setOptOut() - AnalyticsTracker.track(AnalyticsTracker.Stat.APP_REVIEWS_DECLINED_TO_RATE_APP) - - doNotShowInAppReviewsPromptAgain() + private fun launchInAppReviews(activity: Activity) { + AppLog.d(T.UTILS, "Launching in-app reviews prompt") + val manager = ReviewManagerFactory.create(activity) + val request = manager.requestReviewFlow() + request.addOnCompleteListener { task -> + if (task.isSuccessful) { + val reviewInfo = task.result + val flow = manager.launchReviewFlow(activity, reviewInfo) + flow.addOnFailureListener { e -> + AppLog.e(T.UTILS, "Error launching google review API flow.", e) } - return builder.create() - } - - override fun onCancel(dialog: DialogInterface) { - super.onCancel(dialog) - clearSharedPreferences() - storeAskLaterDate() - AnalyticsTracker.track(AnalyticsTracker.Stat.APP_REVIEWS_CANCELLED_PROMPT) - } - } - - /** - * Clear data other than opt-out in shared preferences - called when the "Later" is pressed or dialog is canceled. - */ - private fun clearSharedPreferences() { - preferences.edit().remove(KEY_INSTALL_DATE)?.remove(KEY_LAUNCH_TIMES)?.remove(KEY_INTERACTIONS)?.apply() - } - - /** - * Set opt out flag - the rate dialog will never be shown unless app data is cleared. - */ - private fun setOptOut() { - preferences.edit().putBoolean(KEY_OPT_OUT, optOut)?.apply() - this.optOut = true - } - - /** - * Set do not show in-app reviews prompt flag - the in-app reviews prompt will never be shown unless app data is - * cleared. - */ - private fun doNotShowInAppReviewsPromptAgain() = - preferences.edit().putBoolean(DO_NOT_SHOW_IN_APP_REVIEWS_PROMPT, optOut)?.apply() - - /** - * Store install date - retrieved from package manager if possible. - */ - private fun storeInstallDate(context: Context) { - var installDate = Date() - val packMan = context.packageManager - try { - val pkgInfo = packMan.getPackageInfo(context.packageName, 0) - installDate = Date(pkgInfo.firstInstallTime) - } catch (e: PackageManager.NameNotFoundException) { - AppLog.e(T.UTILS, e) + } else { + task.logException() + } } - preferences.edit().putLong(KEY_INSTALL_DATE, installDate.time)?.apply() - } - /** - * Store the date the user asked for being asked again later. - */ - private fun storeAskLaterDate() { - val nextAskDate = System.currentTimeMillis() - preferences.edit().putLong(KEY_ASK_LATER_DATE, nextAskDate)?.apply() + resetInAppReviewsCounters() } /** @@ -303,4 +131,18 @@ object AppReviewManager { AppPrefs.resetPublishedPostCount() AppPrefs.resetInAppReviewsNotificationCount() } + + /** + * Called from various places in the app where the user has performed a non-trivial action, + * such as publishing a post or page. This was previously used to determine when to show our + * custom rating dialog to involved users but is currently unused. It is left intact, and + * interactions will continue to be counted, in case we want to include interactions in the + * future when determining whether to show the Google review dialog. + */ + fun incrementInteractions(incrementInteractionTracker: AnalyticsTracker.Stat) { + var interactions = preferences.getInt(KEY_RATING_INTERACTIONS, 0) + interactions++ + preferences.edit().putInt(KEY_RATING_INTERACTIONS, interactions)?.apply() + AnalyticsTracker.track(incrementInteractionTracker) + } }