Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package net.opendasharchive.openarchive.features.main

import android.app.Activity

Check warning

Code scanning / detekt

Detects imports in non default order Warning

Imports must be ordered in lexicographic order without any empty lines in-between with "java", "javax", "kotlin" and aliases in the end

Check warning

Code scanning / detekt

Detects unused imports Warning

Unused import
import android.content.Context
import android.content.Intent
import android.graphics.Point
Expand All @@ -16,6 +17,7 @@
import android.view.inputmethod.InputMethodManager
import android.widget.LinearLayout
import android.widget.PopupWindow
import android.widget.Toast
Copy link

Copilot AI Jun 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the unused import 'android.widget.Toast' to clean up the code.

Suggested change
import android.widget.Toast

Copilot uses AI. Check for mistakes.

Check warning

Code scanning / detekt

Detects unused imports Warning

Unused import
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
Expand All @@ -27,6 +29,12 @@
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.snackbar.Snackbar
import com.google.android.play.core.review.ReviewException

Check warning

Code scanning / detekt

Detects unused imports Warning

Unused import
import com.google.android.play.core.review.ReviewInfo

Check warning

Code scanning / detekt

Detects unused imports Warning

Unused import
import com.google.android.play.core.review.ReviewManager
import com.google.android.play.core.review.ReviewManagerFactory
import com.google.android.play.core.review.model.ReviewErrorCode

Check warning

Code scanning / detekt

Detects unused imports Warning

Unused import
import com.google.android.play.core.review.testing.FakeReviewManager
Copy link

Copilot AI Jun 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the unused import 'FakeReviewManager' to reduce clutter in the production code.

Suggested change
import com.google.android.play.core.review.testing.FakeReviewManager

Copilot uses AI. Check for mistakes.

Check warning

Code scanning / detekt

Detects unused imports Warning

Unused import
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import net.opendasharchive.openarchive.BuildConfig
Expand Down Expand Up @@ -78,6 +86,9 @@
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.viewModel
import java.text.NumberFormat
import androidx.core.content.edit
Copy link

Copilot AI Jun 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the unused import 'androidx.core.content.edit' as it is not required.

Suggested change
import androidx.core.content.edit

Copilot uses AI. Check for mistakes.

Check warning

Code scanning / detekt

Detects unused imports Warning

Unused import
import kotlinx.coroutines.delay
import net.opendasharchive.openarchive.util.InAppReviewHelper


class MainActivity : BaseActivity(), SpaceDrawerAdapterListener, FolderDrawerAdapterListener {
Expand Down Expand Up @@ -129,6 +140,8 @@

private lateinit var permissionManager: PermissionManager

private lateinit var reviewManager: ReviewManager
private var shouldPromptReview = false

override fun onCreate(savedInstanceState: Bundle?) {
///enableEdgeToEdge()
Expand Down Expand Up @@ -165,6 +178,9 @@
// Initialize the permission manager with this activity and its dialogManager.
permissionManager = PermissionManager(this, dialogManager)

// Initialize In App Ratings Helper
InAppReviewHelper.init(this)

initMediaLaunchers()
setupToolbarAndPager()
setupNavigationDrawer()
Expand Down Expand Up @@ -196,6 +212,10 @@
// You can start the upload service or update the UI accordingly.
UploadService.startUploadService(this)
}

reviewManager = ReviewManagerFactory.create(this)
InAppReviewHelper.requestReviewInfo(this)
shouldPromptReview = InAppReviewHelper.onAppLaunched()
}

override fun onResume() {
Expand All @@ -212,6 +232,19 @@
serverListOffset = -dims.second.toFloat()
serverListCurOffset = serverListOffset
}

// ─────────────────────────────────────────────────────────────────────────
// Only now, after UI is ready, do we fire the in‐app review if needed.
if (shouldPromptReview) {
lifecycleScope.launch(Dispatchers.Main) {
// Wait a small delay so we don’t interrupt initial load (e.g. 2 seconds).
delay(2_000)
Copy link

Copilot AI Jun 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider extracting the delay duration (2 seconds) into a named constant for better maintainability and clarity.

Suggested change
delay(2_000)
delay(REVIEW_PROMPT_DELAY_MS)

Copilot uses AI. Check for mistakes.

Check warning

Code scanning / detekt

Report magic numbers. Magic number is a numeric literal that is not defined as a constant and hence it's unclear what the purpose of this number is. It's better to declare such numbers as constants and give them a proper name. By default, -1, 0, 1, and 2 are not considered to be magic numbers. Warning

This expression contains a magic number. Consider defining it to a well named constant.
InAppReviewHelper.showReviewIfPossible(this@MainActivity, reviewManager)
InAppReviewHelper.markReviewDone()
shouldPromptReview = false
}
}
// ─────────────────────────────────────────────────────────────────────────
}

@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
Expand Down Expand Up @@ -719,6 +752,8 @@
mFolderAdapter.update(projects)
}



Comment on lines +755 to +756

Check warning

Code scanning / detekt

Reports consecutive blank lines Warning

Needless blank line(s)
private fun refreshCurrentProject() {
val project = getSelectedProject()

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package net.opendasharchive.openarchive.util

import android.app.Activity
import android.content.Context
import com.google.android.play.core.review.ReviewException
import com.google.android.play.core.review.ReviewInfo
import com.google.android.play.core.review.ReviewManager
import com.google.android.play.core.review.ReviewManagerFactory
import net.opendasharchive.openarchive.core.logger.AppLogger

object InAppReviewHelper {
// Keys for our Prefs helper:
private const val KEY_LAUNCH_COUNT = "launch_count"
private const val KEY_LAST_REVIEW_TIME = "last_review_time"

// After this many launches, we become eligible:
private const val MIN_LAUNCHES = 5

// 30 days in ms
private const val THIRTY_DAYS_MS = 30L * 24 * 60 * 60 * 1000

// Once requestReviewFlow() succeeds, we cache this:
private var reviewInfo: ReviewInfo? = null

/**
* Call once (e.g. in Application.onCreate or first Activity) so that Prefs.load(...) runs.
*/
fun init(context: Context) {
Prefs.load(context)
}

/**
* Call early (e.g. in onCreate of MainActivity) to asynchronously fetch ReviewInfo.
*/
fun requestReviewInfo(context: Context) {
val manager: ReviewManager = ReviewManagerFactory.create(context)
manager.requestReviewFlow()
.addOnCompleteListener { task ->
if (task.isSuccessful) {
reviewInfo = task.result
AppLogger.d("InAppReview", "ReviewInfo obtained successfully.")

Check warning

Code scanning / detekt

Multiple occurrences of the same string literal within a single file detected. Prefer extracting the string literal into a property or constant. Warning

Multiple occurrences of the same string literal within a single file detected. Prefer extracting the string literal into a property or constant.
} else {
(task.exception as? ReviewException)?.let { ex ->
AppLogger.e("InAppReview", "Error requesting review flow: ${ex.errorCode}", ex)
}
reviewInfo = null
}
}
}

/**
* Call this immediately on app launch (in onCreate). It increments the stored launch count
* and returns TRUE if we are now eligible to show a review (≥ MIN_LAUNCHES AND ≥ 30 days since last).
*
* It does NOT actually show anything. It only says “yes/no.”
*/
fun onAppLaunched(): Boolean {
val previousCount = Prefs.getInt(KEY_LAUNCH_COUNT, 0)
val newCount = previousCount + 1
Prefs.putInt(KEY_LAUNCH_COUNT, newCount)

val lastReviewTime = Prefs.getLong(KEY_LAST_REVIEW_TIME, 0L)
val now = System.currentTimeMillis()

return if (newCount >= MIN_LAUNCHES && (now - lastReviewTime) >= THIRTY_DAYS_MS) {
true
} else {
false
}
}

/**
* Once you decide it’s time to actually show the prompt (e.g. in onResume, after UI ready),
* call this. If reviewInfo is non-null it will launch; otherwise it just logs “no Info.”
*/
fun showReviewIfPossible(activity: Activity, reviewManager: ReviewManager) {
reviewInfo?.let { info ->
reviewManager.launchReviewFlow(activity, info)
.addOnCompleteListener {
AppLogger.d("InAppReview", "Review flow finished.")
reviewInfo = null
}
} ?: run {
AppLogger.d("InAppReview", "ReviewInfo was null; cannot launch review flow.")
}
}

/**
* After you do showReviewIfPossible(...), call this to reset counters.
* That ensures we won’t prompt again for another 30 days.
*/
fun markReviewDone() {
val now = System.currentTimeMillis()
Prefs.putInt(KEY_LAUNCH_COUNT, 0)
Prefs.putLong(KEY_LAST_REVIEW_TIME, now)
}
}