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