Skip to content

Commit 5217689

Browse files
authored
Merge pull request #676 from OpenArchive/feature/in-app-rating-integration
Feature/in app rating integration
2 parents 1a2c0c7 + 32c81f3 commit 5217689

File tree

2 files changed

+132
-0
lines changed

2 files changed

+132
-0
lines changed

app/src/main/java/net/opendasharchive/openarchive/features/main/MainActivity.kt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package net.opendasharchive.openarchive.features.main
22

3+
import android.app.Activity
34
import android.content.Context
45
import android.content.Intent
56
import android.graphics.Point
@@ -16,6 +17,7 @@ import android.view.inputmethod.EditorInfo
1617
import android.view.inputmethod.InputMethodManager
1718
import android.widget.LinearLayout
1819
import android.widget.PopupWindow
20+
import android.widget.Toast
1921
import androidx.activity.result.contract.ActivityResultContracts
2022
import androidx.annotation.RequiresApi
2123
import androidx.core.content.ContextCompat
@@ -27,6 +29,12 @@ import androidx.lifecycle.lifecycleScope
2729
import androidx.recyclerview.widget.LinearLayoutManager
2830
import androidx.viewpager2.widget.ViewPager2
2931
import com.google.android.material.snackbar.Snackbar
32+
import com.google.android.play.core.review.ReviewException
33+
import com.google.android.play.core.review.ReviewInfo
34+
import com.google.android.play.core.review.ReviewManager
35+
import com.google.android.play.core.review.ReviewManagerFactory
36+
import com.google.android.play.core.review.model.ReviewErrorCode
37+
import com.google.android.play.core.review.testing.FakeReviewManager
3038
import kotlinx.coroutines.Dispatchers
3139
import kotlinx.coroutines.launch
3240
import net.opendasharchive.openarchive.BuildConfig
@@ -78,6 +86,9 @@ import net.opendasharchive.openarchive.util.extensions.show
7886
import org.koin.android.ext.android.inject
7987
import org.koin.androidx.viewmodel.ext.android.viewModel
8088
import java.text.NumberFormat
89+
import androidx.core.content.edit
90+
import kotlinx.coroutines.delay
91+
import net.opendasharchive.openarchive.util.InAppReviewHelper
8192

8293

8394
class MainActivity : BaseActivity(), SpaceDrawerAdapterListener, FolderDrawerAdapterListener {
@@ -129,6 +140,8 @@ class MainActivity : BaseActivity(), SpaceDrawerAdapterListener, FolderDrawerAda
129140

130141
private lateinit var permissionManager: PermissionManager
131142

143+
private lateinit var reviewManager: ReviewManager
144+
private var shouldPromptReview = false
132145

133146
override fun onCreate(savedInstanceState: Bundle?) {
134147
///enableEdgeToEdge()
@@ -165,6 +178,9 @@ class MainActivity : BaseActivity(), SpaceDrawerAdapterListener, FolderDrawerAda
165178
// Initialize the permission manager with this activity and its dialogManager.
166179
permissionManager = PermissionManager(this, dialogManager)
167180

181+
// Initialize In App Ratings Helper
182+
InAppReviewHelper.init(this)
183+
168184
initMediaLaunchers()
169185
setupToolbarAndPager()
170186
setupNavigationDrawer()
@@ -196,6 +212,10 @@ class MainActivity : BaseActivity(), SpaceDrawerAdapterListener, FolderDrawerAda
196212
// You can start the upload service or update the UI accordingly.
197213
UploadService.startUploadService(this)
198214
}
215+
216+
reviewManager = ReviewManagerFactory.create(this)
217+
InAppReviewHelper.requestReviewInfo(this)
218+
shouldPromptReview = InAppReviewHelper.onAppLaunched()
199219
}
200220

201221
override fun onResume() {
@@ -212,6 +232,19 @@ class MainActivity : BaseActivity(), SpaceDrawerAdapterListener, FolderDrawerAda
212232
serverListOffset = -dims.second.toFloat()
213233
serverListCurOffset = serverListOffset
214234
}
235+
236+
// ─────────────────────────────────────────────────────────────────────────
237+
// Only now, after UI is ready, do we fire the in‐app review if needed.
238+
if (shouldPromptReview) {
239+
lifecycleScope.launch(Dispatchers.Main) {
240+
// Wait a small delay so we don’t interrupt initial load (e.g. 2 seconds).
241+
delay(2_000)
242+
InAppReviewHelper.showReviewIfPossible(this@MainActivity, reviewManager)
243+
InAppReviewHelper.markReviewDone()
244+
shouldPromptReview = false
245+
}
246+
}
247+
// ─────────────────────────────────────────────────────────────────────────
215248
}
216249

217250
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@@ -719,6 +752,8 @@ class MainActivity : BaseActivity(), SpaceDrawerAdapterListener, FolderDrawerAda
719752
mFolderAdapter.update(projects)
720753
}
721754

755+
756+
722757
private fun refreshCurrentProject() {
723758
val project = getSelectedProject()
724759

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package net.opendasharchive.openarchive.util
2+
3+
import android.app.Activity
4+
import android.content.Context
5+
import com.google.android.play.core.review.ReviewException
6+
import com.google.android.play.core.review.ReviewInfo
7+
import com.google.android.play.core.review.ReviewManager
8+
import com.google.android.play.core.review.ReviewManagerFactory
9+
import net.opendasharchive.openarchive.core.logger.AppLogger
10+
11+
object InAppReviewHelper {
12+
// Keys for our Prefs helper:
13+
private const val KEY_LAUNCH_COUNT = "launch_count"
14+
private const val KEY_LAST_REVIEW_TIME = "last_review_time"
15+
16+
// After this many launches, we become eligible:
17+
private const val MIN_LAUNCHES = 5
18+
19+
// 30 days in ms
20+
private const val THIRTY_DAYS_MS = 30L * 24 * 60 * 60 * 1000
21+
22+
// Once requestReviewFlow() succeeds, we cache this:
23+
private var reviewInfo: ReviewInfo? = null
24+
25+
/**
26+
* Call once (e.g. in Application.onCreate or first Activity) so that Prefs.load(...) runs.
27+
*/
28+
fun init(context: Context) {
29+
Prefs.load(context)
30+
}
31+
32+
/**
33+
* Call early (e.g. in onCreate of MainActivity) to asynchronously fetch ReviewInfo.
34+
*/
35+
fun requestReviewInfo(context: Context) {
36+
val manager: ReviewManager = ReviewManagerFactory.create(context)
37+
manager.requestReviewFlow()
38+
.addOnCompleteListener { task ->
39+
if (task.isSuccessful) {
40+
reviewInfo = task.result
41+
AppLogger.d("InAppReview", "ReviewInfo obtained successfully.")
42+
} else {
43+
(task.exception as? ReviewException)?.let { ex ->
44+
AppLogger.e("InAppReview", "Error requesting review flow: ${ex.errorCode}", ex)
45+
}
46+
reviewInfo = null
47+
}
48+
}
49+
}
50+
51+
/**
52+
* Call this immediately on app launch (in onCreate). It increments the stored launch count
53+
* and returns TRUE if we are now eligible to show a review (≥ MIN_LAUNCHES AND ≥ 30 days since last).
54+
*
55+
* It does NOT actually show anything. It only says “yes/no.”
56+
*/
57+
fun onAppLaunched(): Boolean {
58+
val previousCount = Prefs.getInt(KEY_LAUNCH_COUNT, 0)
59+
val newCount = previousCount + 1
60+
Prefs.putInt(KEY_LAUNCH_COUNT, newCount)
61+
62+
val lastReviewTime = Prefs.getLong(KEY_LAST_REVIEW_TIME, 0L)
63+
val now = System.currentTimeMillis()
64+
65+
return if (newCount >= MIN_LAUNCHES && (now - lastReviewTime) >= THIRTY_DAYS_MS) {
66+
true
67+
} else {
68+
false
69+
}
70+
}
71+
72+
/**
73+
* Once you decide it’s time to actually show the prompt (e.g. in onResume, after UI ready),
74+
* call this. If reviewInfo is non-null it will launch; otherwise it just logs “no Info.”
75+
*/
76+
fun showReviewIfPossible(activity: Activity, reviewManager: ReviewManager) {
77+
reviewInfo?.let { info ->
78+
reviewManager.launchReviewFlow(activity, info)
79+
.addOnCompleteListener {
80+
AppLogger.d("InAppReview", "Review flow finished.")
81+
reviewInfo = null
82+
}
83+
} ?: run {
84+
AppLogger.d("InAppReview", "ReviewInfo was null; cannot launch review flow.")
85+
}
86+
}
87+
88+
/**
89+
* After you do showReviewIfPossible(...), call this to reset counters.
90+
* That ensures we won’t prompt again for another 30 days.
91+
*/
92+
fun markReviewDone() {
93+
val now = System.currentTimeMillis()
94+
Prefs.putInt(KEY_LAUNCH_COUNT, 0)
95+
Prefs.putLong(KEY_LAST_REVIEW_TIME, now)
96+
}
97+
}

0 commit comments

Comments
 (0)