diff --git a/app/build.gradle b/app/build.gradle index d0958cd17..559e36a3f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -125,9 +125,6 @@ dependencies { implementation 'com.github.skydoves:balloon:1.5.3' implementation 'com.github.xabaras:RecyclerViewSwipeDecorator:1.3' - // Non-free dependencies: - playImplementation "com.google.android.play:review:2.0.1" - androidTestImplementation "org.awaitility:awaitility:$awaitilityVersion" androidTestImplementation 'com.nanohttpd:nanohttpd:2.1.1' androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion" diff --git a/app/src/androidTest/java/de/test/antennapod/EspressoTestUtils.java b/app/src/androidTest/java/de/test/antennapod/EspressoTestUtils.java index a7575862b..f0e0a6996 100644 --- a/app/src/androidTest/java/de/test/antennapod/EspressoTestUtils.java +++ b/app/src/androidTest/java/de/test/antennapod/EspressoTestUtils.java @@ -25,7 +25,6 @@ import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.storage.preferences.UserPreferences; import de.danoeh.antennapod.core.service.playback.PlaybackService; -import de.danoeh.antennapod.dialog.RatingDialog; import de.danoeh.antennapod.fragment.NavDrawerFragment; import org.awaitility.Awaitility; import org.awaitility.core.ConditionTimeoutException; @@ -166,9 +165,6 @@ public class EspressoTestUtils { .edit() .putString(UserPreferences.PREF_UPDATE_INTERVAL, "0") .commit(); - - RatingDialog.init(InstrumentationRegistry.getInstrumentation().getTargetContext()); - RatingDialog.saveRated(); } public static void setLaunchScreen(String tag) { diff --git a/app/src/free/java/de/danoeh/antennapod/dialog/RatingDialog.java b/app/src/free/java/de/danoeh/antennapod/dialog/RatingDialog.java deleted file mode 100644 index ec470eee6..000000000 --- a/app/src/free/java/de/danoeh/antennapod/dialog/RatingDialog.java +++ /dev/null @@ -1,13 +0,0 @@ -package de.danoeh.antennapod.dialog; - -import android.content.Context; -import androidx.annotation.VisibleForTesting; - -public class RatingDialog { - public static void init(Context context) {} - - public static void check() {} - - @VisibleForTesting - public static void saveRated() {} -} \ No newline at end of file diff --git a/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java index d38d4e3ad..3c867b88c 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java @@ -39,7 +39,7 @@ import de.danoeh.antennapod.ui.appstartintent.MediaButtonStarter; import de.danoeh.antennapod.ui.common.ThemeSwitcher; import de.danoeh.antennapod.core.sync.queue.SynchronizationQueueSink; import de.danoeh.antennapod.core.util.download.FeedUpdateManager; -import de.danoeh.antennapod.dialog.RatingDialog; +import de.danoeh.antennapod.dialog.rating.RatingDialogManager; import de.danoeh.antennapod.event.EpisodeDownloadEvent; import de.danoeh.antennapod.event.FeedUpdateRunningEvent; import de.danoeh.antennapod.event.MessageEvent; @@ -496,14 +496,13 @@ public class MainActivity extends CastEnabledActivity { public void onStart() { super.onStart(); EventBus.getDefault().register(this); - RatingDialog.init(this); + new RatingDialogManager(this).showIfNeeded(); } @Override protected void onResume() { super.onResume(); handleNavIntent(); - RatingDialog.check(); if (lastTheme != ThemeSwitcher.getNoTitleTheme(this)) { finish(); diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/rating/RatingDialogFragment.java b/app/src/main/java/de/danoeh/antennapod/dialog/rating/RatingDialogFragment.java new file mode 100644 index 000000000..4e9da8b9a --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/dialog/rating/RatingDialogFragment.java @@ -0,0 +1,79 @@ +package de.danoeh.antennapod.dialog.rating; + +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.text.HtmlCompat; +import androidx.fragment.app.DialogFragment; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.util.IntentUtils; +import de.danoeh.antennapod.databinding.RatingDialogBinding; +import de.danoeh.antennapod.ui.common.DateFormatter; + +import java.util.Date; + +public class RatingDialogFragment extends DialogFragment { + private static final String EXTRA_TOTAL_TIME = "totalTime"; + private static final String EXTRA_OLDEST_DATE = "oldestDate"; + + public static RatingDialogFragment newInstance(long totalTime, long oldestDate) { + RatingDialogFragment fragment = new RatingDialogFragment(); + Bundle arguments = new Bundle(); + arguments.putLong(EXTRA_TOTAL_TIME, totalTime); + arguments.putLong(EXTRA_OLDEST_DATE, oldestDate); + fragment.setArguments(arguments); + return fragment; + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + return new MaterialAlertDialogBuilder(getContext()) + .setView(onCreateView(getLayoutInflater(), null, savedInstanceState)) + .create(); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, + @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + RatingDialogBinding viewBinding = RatingDialogBinding.inflate(inflater); + long totalTime = getArguments().getLong(EXTRA_TOTAL_TIME, 0); + long oldestDate = getArguments().getLong(EXTRA_OLDEST_DATE, 0); + + viewBinding.headerLabel.setText(HtmlCompat.fromHtml(getString(R.string.rating_tagline, + DateFormatter.formatAbbrev(getContext(), new Date(oldestDate)), + "
", totalTime / 3600L, + "
"), HtmlCompat.FROM_HTML_MODE_LEGACY)); + viewBinding.neverAgainButton.setOnClickListener(v -> { + new RatingDialogManager(getActivity()).saveRated(); + dismiss(); + }); + viewBinding.showLaterButton.setOnClickListener(v -> { + new RatingDialogManager(getActivity()).resetStartDate(); + dismiss(); + }); + viewBinding.rateButton.setOnClickListener(v -> { + IntentUtils.openInBrowser(getContext(), + "https://play.google.com/store/apps/details?id=de.danoeh.antennapod"); + new RatingDialogManager(getActivity()).saveRated(); + }); + viewBinding.contibuteButton.setOnClickListener(v -> { + IntentUtils.openInBrowser(getContext(), IntentUtils.getLocalizedWebsiteLink(getContext()) + "/contribute/"); + new RatingDialogManager(getActivity()).saveRated(); + }); + return viewBinding.getRoot(); + } + + @Override + public void onCancel(@NonNull DialogInterface dialog) { + super.onCancel(dialog); + new RatingDialogManager(getActivity()).resetStartDate(); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/rating/RatingDialogManager.java b/app/src/main/java/de/danoeh/antennapod/dialog/rating/RatingDialogManager.java new file mode 100644 index 000000000..ffa67cc2e --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/dialog/rating/RatingDialogManager.java @@ -0,0 +1,94 @@ +package de.danoeh.antennapod.dialog.rating; + +import android.content.Context; +import android.content.SharedPreferences; + +import java.util.concurrent.TimeUnit; + +import android.util.Log; +import androidx.fragment.app.FragmentActivity; +import de.danoeh.antennapod.core.BuildConfig; +import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.core.storage.StatisticsItem; +import io.reactivex.Observable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; +import kotlin.Pair; + +public class RatingDialogManager { + private static final int AFTER_DAYS = 20; + private static final String TAG = "RatingDialog"; + private static final String PREFS_NAME = "RatingPrefs"; + private static final String KEY_RATED = "KEY_WAS_RATED"; + private static final String KEY_FIRST_START_DATE = "KEY_FIRST_HIT_DATE"; + + private final SharedPreferences preferences; + private final FragmentActivity fragmentActivity; + private Disposable disposable; + + public RatingDialogManager(FragmentActivity activity) { + this.fragmentActivity = activity; + preferences = activity.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); + } + + public void showIfNeeded() { + //noinspection ConstantConditions + if (isRated() || BuildConfig.DEBUG || "free".equals(BuildConfig.FLAVOR)) { + return; + } else if (!enoughTimeSinceInstall()) { + return; + } + + if (disposable != null) { + disposable.dispose(); + } + disposable = Observable.fromCallable( + () -> { + DBReader.StatisticsResult statisticsData = DBReader.getStatistics(false, 0, Long.MAX_VALUE); + long totalTime = 0; + for (StatisticsItem item : statisticsData.feedTime) { + totalTime += item.timePlayed; + } + return new Pair<>(totalTime, statisticsData.oldestDate); + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(result -> { + long totalTime = result.getFirst(); + long oldestDate = result.getSecond(); + if (totalTime < TimeUnit.SECONDS.convert(15, TimeUnit.HOURS)) { + return; + } else if (oldestDate > System.currentTimeMillis() + - TimeUnit.MILLISECONDS.convert(AFTER_DAYS, TimeUnit.DAYS)) { + return; // In case the app was opened but nothing was played + } + RatingDialogFragment.newInstance(result.getFirst(), result.getSecond()) + .show(fragmentActivity.getSupportFragmentManager(), TAG); + }, error -> Log.e(TAG, Log.getStackTraceString(error))); + } + + private boolean isRated() { + return preferences.getBoolean(KEY_RATED, false); + } + + public void saveRated() { + preferences.edit().putBoolean(KEY_RATED, true).apply(); + } + + public void resetStartDate() { + preferences.edit().putLong(KEY_FIRST_START_DATE, System.currentTimeMillis()).apply(); + } + + private boolean enoughTimeSinceInstall() { + if (preferences.getLong(KEY_FIRST_START_DATE, 0) == 0) { + resetStartDate(); + return false; + } + long now = System.currentTimeMillis(); + long firstDate = preferences.getLong(KEY_FIRST_START_DATE, now); + long diff = now - firstDate; + long diffDays = TimeUnit.DAYS.convert(diff, TimeUnit.MILLISECONDS); + return diffDays >= AFTER_DAYS; + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java index 4d7313247..bf30700bf 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java @@ -17,13 +17,6 @@ import de.danoeh.antennapod.activity.BugReportActivity; import de.danoeh.antennapod.activity.PreferenceActivity; import de.danoeh.antennapod.core.util.IntentUtils; import de.danoeh.antennapod.fragment.preferences.about.AboutFragment; -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.ArrayUtils; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.util.Locale; public class MainPreferencesFragment extends PreferenceFragmentCompat { @@ -113,7 +106,8 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat { } ); findPreference(PREF_DOCUMENTATION).setOnPreferenceClickListener(preference -> { - IntentUtils.openInBrowser(getContext(), getLocalizedWebsiteLink() + "/documentation/"); + IntentUtils.openInBrowser(getContext(), + IntentUtils.getLocalizedWebsiteLink(getContext()) + "/documentation/"); return true; }); findPreference(PREF_VIEW_FORUM).setOnPreferenceClickListener(preference -> { @@ -121,7 +115,8 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat { return true; }); findPreference(PREF_CONTRIBUTE).setOnPreferenceClickListener(preference -> { - IntentUtils.openInBrowser(getContext(), getLocalizedWebsiteLink() + "/contribute/"); + IntentUtils.openInBrowser(getContext(), + IntentUtils.getLocalizedWebsiteLink(getContext()) + "/contribute/"); return true; }); findPreference(PREF_SEND_BUG_REPORT).setOnPreferenceClickListener(preference -> { @@ -130,20 +125,6 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat { }); } - private String getLocalizedWebsiteLink() { - try (InputStream is = getContext().getAssets().open("website-languages.txt")) { - String[] languages = IOUtils.toString(is, StandardCharsets.UTF_8.name()).split("\n"); - String deviceLanguage = Locale.getDefault().getLanguage(); - if (ArrayUtils.contains(languages, deviceLanguage) && !"en".equals(deviceLanguage)) { - return "https://antennapod.org/" + deviceLanguage; - } else { - return "https://antennapod.org"; - } - } catch (IOException e) { - throw new RuntimeException(e); - } - } - private void setupSearch() { SearchPreference searchPreference = findPreference("searchPreference"); SearchConfiguration config = searchPreference.getSearchConfiguration(); diff --git a/app/src/main/res/layout/rating_dialog.xml b/app/src/main/res/layout/rating_dialog.xml new file mode 100644 index 000000000..c8f3c980b --- /dev/null +++ b/app/src/main/res/layout/rating_dialog.xml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + +