diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/main/MainActivity.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/main/MainActivity.java
index 43bab4b9..f5722207 100644
--- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/main/MainActivity.java
+++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/main/MainActivity.java
@@ -30,14 +30,18 @@
import androidx.navigation.ui.NavigationUI;
import androidx.preference.PreferenceManager;
+import com.d4rk.androidtutorials.java.BuildConfig;
import com.d4rk.androidtutorials.java.R;
import com.d4rk.androidtutorials.java.databinding.ActivityMainBinding;
+import com.d4rk.androidtutorials.java.notifications.managers.AppUpdateNotificationsManager;
+import com.d4rk.androidtutorials.java.notifications.managers.AppUsageNotificationsManager;
import com.d4rk.androidtutorials.java.ui.components.navigation.BottomSheetMenuFragment;
import com.d4rk.androidtutorials.java.ui.screens.startup.StartupActivity;
import com.d4rk.androidtutorials.java.ui.screens.startup.StartupViewModel;
import com.d4rk.androidtutorials.java.ui.screens.support.SupportActivity;
import com.d4rk.androidtutorials.java.utils.ConsentUtils;
import com.d4rk.androidtutorials.java.utils.EdgeToEdgeDelegate;
+import com.d4rk.androidtutorials.java.utils.ReviewHelper;
import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.MobileAds;
import com.google.android.material.navigation.NavigationBarView;
@@ -48,6 +52,7 @@
import com.google.android.play.core.install.InstallStateUpdatedListener;
import com.google.android.play.core.install.model.AppUpdateType;
import com.google.android.play.core.install.model.InstallStatus;
+import com.google.android.play.core.install.model.UpdateAvailability;
import com.google.android.ump.ConsentInformation;
import com.google.android.ump.ConsentRequestParameters;
import com.google.android.ump.UserMessagingPlatform;
@@ -92,6 +97,7 @@ public void onResume(@NonNull LifecycleOwner owner) {
private NavController navController;
private int currentNavIndex;
private AppUpdateManager appUpdateManager;
+ private AppUpdateNotificationsManager appUpdateNotificationsManager;
private InstallStateUpdatedListener installStateUpdatedListener;
private long backPressedTime;
@@ -134,6 +140,7 @@ protected void onCreate(Bundle savedInstanceState) {
}
this.appUpdateManager = mainViewModel.getAppUpdateManager();
+ setupUpdateNotifications();
registerInstallStateListener();
getLifecycle().addObserver(lifecycleObserver);
@@ -150,6 +157,8 @@ public void handleOnBackPressed() {
}
}
});
+
+ checkInAppReview();
}
private void setupActionBar() {
@@ -238,17 +247,19 @@ private void observeViewModel() {
.build();
if (useRail) {
- NavigationUI.setupWithNavController(mBinding.navRail, navController);
- mBinding.navRail.setOnItemSelectedListener(item -> {
- if (item.getItemId() == navController.getCurrentDestination().getId()) {
+ if (mBinding.navRail != null) {
+ NavigationUI.setupWithNavController(mBinding.navRail, navController);
+ mBinding.navRail.setOnItemSelectedListener(item -> {
+ if (item.getItemId() == navController.getCurrentDestination().getId()) {
+ return true;
+ }
+ int newIndex = navOrder.get(item.getItemId());
+ NavOptions options = newIndex > currentNavIndex ? forwardOptions : backwardOptions;
+ navController.navigate(item.getItemId(), null, options);
+ currentNavIndex = newIndex;
return true;
- }
- int newIndex = navOrder.get(item.getItemId());
- NavOptions options = newIndex > currentNavIndex ? forwardOptions : backwardOptions;
- navController.navigate(item.getItemId(), null, options);
- currentNavIndex = newIndex;
- return true;
- });
+ });
+ }
} else {
NavigationUI.setupWithNavController(navBarView, navController);
navBarView.setOnItemSelectedListener(item -> {
@@ -299,12 +310,64 @@ public boolean onOptionsItemSelected(android.view.MenuItem item) {
return super.onOptionsItemSelected(item);
}
- private void startImmediateUpdate(AppUpdateInfo appUpdateInfo) {
- appUpdateManager.startUpdateFlowForResult(
- appUpdateInfo,
- updateActivityResultLauncher,
- AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build()
+ @Override
+ protected void onResume() {
+ super.onResume();
+ AppUsageNotificationsManager appUsageNotificationsManager = new AppUsageNotificationsManager(this);
+ appUsageNotificationsManager.scheduleAppUsageCheck();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ appUpdateNotificationsManager.checkAndSendUpdateNotification();
+ }
+ checkForFlexibleOrImmediateUpdate();
+ }
+
+ private void checkForFlexibleOrImmediateUpdate() {
+ appUpdateManager.getAppUpdateInfo().addOnSuccessListener(appUpdateInfo -> {
+ boolean updateAvailable = appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE;
+ if (updateAvailable) {
+ startImmediateUpdate(appUpdateInfo);
+ }
+ })
+ .addOnFailureListener(e -> {
+ if (!BuildConfig.DEBUG) {
+ Snackbar.make(
+ findViewById(android.R.id.content),
+ getString(R.string.snack_general_error),
+ Snackbar.LENGTH_LONG
+ ).show();
+ }
+ });
+ }
+
+ private void checkInAppReview() {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+ int sessionCount = prefs.getInt(getString(R.string.key_session_count), 0);
+ boolean hasPrompted = prefs.getBoolean(getString(R.string.key_has_prompted_review), false);
+
+ ReviewHelper.launchInAppReviewIfEligible(
+ this,
+ sessionCount,
+ hasPrompted,
+ () -> prefs.edit().putBoolean(getString(R.string.key_has_prompted_review), true).apply()
);
+
+ prefs.edit().putInt(getString(R.string.key_session_count), sessionCount + 1).apply();
+ }
+
+ private void startImmediateUpdate(AppUpdateInfo appUpdateInfo) {
+ try {
+ appUpdateManager.startUpdateFlowForResult(
+ appUpdateInfo,
+ updateActivityResultLauncher,
+ AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build()
+ );
+ } catch (Exception e) {
+ Log.e("MainActivity", "Error starting in-app update", e);
+ }
+ }
+
+ private void setupUpdateNotifications() {
+ appUpdateNotificationsManager = new AppUpdateNotificationsManager(this);
}
private void registerInstallStateListener() {
diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/utils/ReviewHelper.java b/app/src/main/java/com/d4rk/androidtutorials/java/utils/ReviewHelper.java
new file mode 100644
index 00000000..53036953
--- /dev/null
+++ b/app/src/main/java/com/d4rk/androidtutorials/java/utils/ReviewHelper.java
@@ -0,0 +1,47 @@
+package com.d4rk.androidtutorials.java.utils;
+
+import android.app.Activity;
+
+import com.google.android.play.core.review.ReviewInfo;
+import com.google.android.play.core.review.ReviewManager;
+import com.google.android.play.core.review.ReviewManagerFactory;
+
+/**
+ * Utility class for launching Google Play in-app reviews.
+ */
+public final class ReviewHelper {
+
+ private ReviewHelper() {
+ // Utility class
+ }
+
+ public static void launchInAppReviewIfEligible(Activity activity,
+ int sessionCount,
+ boolean hasPromptedBefore,
+ Runnable onReviewLaunched) {
+ if (sessionCount < 3 || hasPromptedBefore) {
+ return;
+ }
+ launchReview(activity, onReviewLaunched);
+ }
+
+ public static void forceLaunchInAppReview(Activity activity) {
+ launchReview(activity, null);
+ }
+
+ private static void launchReview(Activity activity, Runnable onReviewLaunched) {
+ ReviewManager reviewManager = ReviewManagerFactory.create(activity);
+ reviewManager.requestReviewFlow()
+ .addOnCompleteListener(task -> {
+ if (task.isSuccessful()) {
+ ReviewInfo reviewInfo = task.getResult();
+ reviewManager.launchReviewFlow(activity, reviewInfo)
+ .addOnCompleteListener(flow -> {
+ if (onReviewLaunched != null) {
+ onReviewLaunched.run();
+ }
+ });
+ }
+ });
+ }
+}
diff --git a/app/src/main/res/values/keys.xml b/app/src/main/res/values/keys.xml
index 2ad5e922..1612118c 100644
--- a/app/src/main/res/values/keys.xml
+++ b/app/src/main/res/values/keys.xml
@@ -21,4 +21,6 @@
consent_ad_user_data
consent_ad_personalization
onboarding_complete
+ session_count
+ has_prompted_review
\ No newline at end of file