Skip to content
Open
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
Expand Up @@ -13,22 +13,18 @@ import org.fossify.commons.R
import org.fossify.commons.extensions.baseConfig
import org.fossify.commons.extensions.checkAppIconColor
import org.fossify.commons.extensions.getAppIconColors
import org.fossify.commons.extensions.getCanAppBeUpgraded
import org.fossify.commons.extensions.getInternalStoragePath
import org.fossify.commons.extensions.isAProApp
import org.fossify.commons.extensions.isAppInstalledOnSDCard
import org.fossify.commons.extensions.isOrWasThankYouInstalled
import org.fossify.commons.extensions.launchViewIntent
import org.fossify.commons.extensions.random
import org.fossify.commons.extensions.showAutomaticSupportPromptIfEligible
import org.fossify.commons.extensions.toggleAppIconColor
import org.fossify.commons.extensions.updateSDCardPath
import org.fossify.commons.helpers.isOreoMr1Plus
import org.fossify.commons.models.Release

fun ComponentActivity.appLaunchedCompose(
appId: String,
showUpgradeDialog: () -> Unit,
showDonateDialog: () -> Unit,
) {
baseConfig.internalStoragePath = getInternalStoragePath()
updateSDCardPath()
Expand Down Expand Up @@ -64,15 +60,20 @@ fun ComponentActivity.appLaunchedCompose(
}

baseConfig.appRunCount++
if (baseConfig.appRunCount % 30 == 0 && !isAProApp()) {
if (!resources.getBoolean(R.bool.hide_google_relations)) {
if (getCanAppBeUpgraded()) {
showUpgradeDialog()
} else if (!isOrWasThankYouInstalled()) {
showDonateDialog()
}
}
}
showAutomaticSupportPromptIfEligible()
}

@Deprecated(
message = "Automatic support prompts are now handled by Commons.",
replaceWith = ReplaceWith("appLaunchedCompose(appId)"),
)
@Suppress("UNUSED_PARAMETER")
fun ComponentActivity.appLaunchedCompose(
appId: String,
showUpgradeDialog: () -> Unit,
showDonateDialog: () -> Unit,
) {
appLaunchedCompose(appId)
}

fun ComponentActivity.checkWhatsNewCompose(releases: List<Release>, currVersion: Int, showWhatsNewDialog: (List<Release>) -> Unit) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,31 @@ import org.fossify.commons.databinding.DialogDonateBinding
import org.fossify.commons.extensions.*

class DonateDialog(val activity: Activity) {
private var onPurchase: () -> Unit = { activity.launchViewIntent(R.string.thank_you_url) }
private var onLater: () -> Unit = {}

internal constructor(
activity: Activity,
onPurchase: () -> Unit,
onLater: () -> Unit,
) : this(activity) {
this.onPurchase = onPurchase
this.onLater = onLater
}

init {
val view = DialogDonateBinding.inflate(activity.layoutInflater, null, false).apply {
dialogDonateImage.applyColorFilter(activity.getProperTextColor())
dialogDonateText.text = Html.fromHtml(activity.getString(R.string.donate_short))
dialogDonateText.movementMethod = LinkMovementMethod.getInstance()
dialogDonateImage.setOnClickListener {
activity.launchViewIntent(R.string.thank_you_url)
onPurchase()
}
}

activity.getAlertDialogBuilder()
.setPositiveButton(R.string.purchase) { _, _ -> activity.launchViewIntent(R.string.thank_you_url) }
.setNegativeButton(R.string.later, null)
.setPositiveButton(R.string.purchase) { _, _ -> onPurchase() }
.setNegativeButton(R.string.later) { _, _ -> onLater() }
.apply {
activity.setupDialogStuff(view.root, this, cancelOnTouchOutside = false)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.fossify.commons.dialogs

import android.app.Activity
import org.fossify.commons.R
import org.fossify.commons.databinding.DialogMessageBinding
import org.fossify.commons.extensions.getAlertDialogBuilder
import org.fossify.commons.extensions.setupDialogStuff

internal class RateAppDialog(
activity: Activity,
onRate: () -> Unit,
onLater: () -> Unit,
) {
init {
val view = DialogMessageBinding.inflate(activity.layoutInflater, null, false).apply {
message.setText(R.string.rate_app_prompt)
}

activity.getAlertDialogBuilder()
.setPositiveButton(R.string.rate_this_app) { _, _ -> onRate() }
.setNegativeButton(R.string.later) { _, _ -> onLater() }
.apply {
activity.setupDialogStuff(
view = view.root,
dialog = this,
titleId = R.string.rate_this_app,
cancelOnTouchOutside = false,
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,8 @@ import org.fossify.commons.dialogs.AppSideloadedDialog
import org.fossify.commons.dialogs.ConfirmationAdvancedDialog
import org.fossify.commons.dialogs.ConfirmationDialog
import org.fossify.commons.dialogs.CustomIntervalPickerDialog
import org.fossify.commons.dialogs.DonateDialog
import org.fossify.commons.dialogs.RadioGroupDialog
import org.fossify.commons.dialogs.SecurityDialog
import org.fossify.commons.dialogs.UpgradeToProDialog
import org.fossify.commons.dialogs.WhatsNewDialog
import org.fossify.commons.dialogs.WritePermissionDialog
import org.fossify.commons.dialogs.WritePermissionDialog.WritePermissionDialogMode
Expand Down Expand Up @@ -127,19 +125,7 @@ fun Activity.appLaunched(appId: String) {
}

baseConfig.appRunCount++
if (baseConfig.appRunCount % 30 == 0 && !isAProApp()) {
if (!resources.getBoolean(R.bool.hide_google_relations)) {
showDonateOrUpgradeDialog()
}
}
}

fun Activity.showDonateOrUpgradeDialog() {
if (getCanAppBeUpgraded()) {
UpgradeToProDialog(this)
} else if (!isOrWasThankYouInstalled()) {
DonateDialog(this)
}
showAutomaticSupportPromptIfEligible()
}

fun Activity.isAppInstalledOnSDCard(): Boolean = try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package org.fossify.commons.extensions

import android.app.Activity
import org.fossify.commons.R
import org.fossify.commons.dialogs.DonateDialog
import org.fossify.commons.dialogs.RateAppDialog
import org.fossify.commons.helpers.BaseConfig

private const val FIRST_PROMPT_MIN_LAUNCHES = 10
private const val NEXT_PROMPT_MIN_LAUNCHES = 5
private const val MAX_PROMPT_DISMISSALS = 3
private const val DAY_MILLIS = 24L * 60L * 60L * 1000L
private const val FIRST_PROMPT_DELAY_MILLIS = 7L * DAY_MILLIS
private const val NEXT_PROMPT_DELAY_MILLIS = 30L * DAY_MILLIS

private enum class SupportPromptType(val value: Int) {
RateApp(0),
ThankYou(1);

fun alternate() = when (this) {
RateApp -> ThankYou
ThankYou -> RateApp
}

companion object {
fun fromValue(value: Int) = entries.firstOrNull { it.value == value } ?: RateApp
}
}

internal fun Activity.showAutomaticSupportPromptIfEligible() {
val config = baseConfig
val now = System.currentTimeMillis()
if (config.supportPromptFirstLaunchTimestamp == 0L) {
config.supportPromptFirstLaunchTimestamp = now
return
}

val hasEnoughInitialUse = config.appRunCount >= FIRST_PROMPT_MIN_LAUNCHES
val hasEnoughInitialTime = now - config.supportPromptFirstLaunchTimestamp >= FIRST_PROMPT_DELAY_MILLIS
val isSupportPromptDisabled =
resources.getBoolean(R.bool.hide_google_relations) ||
config.supportPromptDismissalCount >= MAX_PROMPT_DISMISSALS
val lacksInitialEligibility = !hasEnoughInitialUse || !hasEnoughInitialTime
if (isSupportPromptDisabled || lacksInitialEligibility) {
return
}

if (config.supportPromptLastShownTimestamp != 0L) {
val hasEnoughFurtherUse =
config.appRunCount - config.supportPromptLastShownAppRunCount >= NEXT_PROMPT_MIN_LAUNCHES
val hasEnoughFurtherTime = now - config.supportPromptLastShownTimestamp >= NEXT_PROMPT_DELAY_MILLIS
if (!hasEnoughFurtherUse || !hasEnoughFurtherTime) {
return
}
}

val promptType = selectSupportPrompt(config) ?: return
config.supportPromptLastShownTimestamp = now
config.supportPromptLastShownAppRunCount = config.appRunCount
config.supportPromptNextType = promptType.alternate().value

val onLater = {
config.supportPromptDismissalCount =
(config.supportPromptDismissalCount + 1).coerceAtMost(MAX_PROMPT_DISMISSALS)
}

when (promptType) {
SupportPromptType.RateApp -> RateAppDialog(
activity = this,
onRate = {
config.wasRatePromptAccepted = true
launchAppRatingPage()
},
onLater = onLater,
)

SupportPromptType.ThankYou -> DonateDialog(
activity = this,
onPurchase = { launchPurchaseThankYouIntent() },
onLater = onLater,
)
}
}

private fun Activity.selectSupportPrompt(config: BaseConfig): SupportPromptType? {
val canShowRatePrompt = !config.wasRatePromptAccepted
val canShowThankYouPrompt = !isOrWasThankYouInstalled()
val preferredPrompt = SupportPromptType.fromValue(config.supportPromptNextType)

return when {
preferredPrompt == SupportPromptType.RateApp && canShowRatePrompt -> SupportPromptType.RateApp
preferredPrompt == SupportPromptType.ThankYou && canShowThankYouPrompt -> SupportPromptType.ThankYou
canShowRatePrompt -> SupportPromptType.RateApp
canShowThankYouPrompt -> SupportPromptType.ThankYou
else -> null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,30 @@ open class BaseConfig(val context: Context) {
get() = prefs.getInt(APP_RUN_COUNT, 0)
set(appRunCount) = prefs.edit().putInt(APP_RUN_COUNT, appRunCount).apply()

var supportPromptFirstLaunchTimestamp: Long
get() = prefs.getLong(SUPPORT_PROMPT_FIRST_LAUNCH_TIMESTAMP, 0L)
set(timestamp) = prefs.edit().putLong(SUPPORT_PROMPT_FIRST_LAUNCH_TIMESTAMP, timestamp).apply()

var supportPromptLastShownTimestamp: Long
get() = prefs.getLong(SUPPORT_PROMPT_LAST_SHOWN_TIMESTAMP, 0L)
set(timestamp) = prefs.edit().putLong(SUPPORT_PROMPT_LAST_SHOWN_TIMESTAMP, timestamp).apply()

var supportPromptLastShownAppRunCount: Int
get() = prefs.getInt(SUPPORT_PROMPT_LAST_SHOWN_APP_RUN_COUNT, 0)
set(appRunCount) = prefs.edit().putInt(SUPPORT_PROMPT_LAST_SHOWN_APP_RUN_COUNT, appRunCount).apply()

var supportPromptDismissalCount: Int
get() = prefs.getInt(SUPPORT_PROMPT_DISMISSAL_COUNT, 0)
set(dismissalCount) = prefs.edit().putInt(SUPPORT_PROMPT_DISMISSAL_COUNT, dismissalCount).apply()

var supportPromptNextType: Int
get() = prefs.getInt(SUPPORT_PROMPT_NEXT_TYPE, 0)
set(promptType) = prefs.edit().putInt(SUPPORT_PROMPT_NEXT_TYPE, promptType).apply()

var wasRatePromptAccepted: Boolean
get() = prefs.getBoolean(WAS_RATE_PROMPT_ACCEPTED, false)
set(wasAccepted) = prefs.edit().putBoolean(WAS_RATE_PROMPT_ACCEPTED, wasAccepted).apply()

var lastVersion: Int
get() = prefs.getInt(LAST_VERSION, 0)
set(lastVersion) = prefs.edit().putInt(LAST_VERSION, lastVersion).apply()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ const val YEAR_SECONDS = YEAR_MINUTES * 60
// shared preferences
const val PREFS_KEY = "Prefs"
const val APP_RUN_COUNT = "app_run_count"
const val SUPPORT_PROMPT_FIRST_LAUNCH_TIMESTAMP = "support_prompt_first_launch_timestamp"
const val SUPPORT_PROMPT_LAST_SHOWN_TIMESTAMP = "support_prompt_last_shown_timestamp"
const val SUPPORT_PROMPT_LAST_SHOWN_APP_RUN_COUNT = "support_prompt_last_shown_app_run_count"
const val SUPPORT_PROMPT_DISMISSAL_COUNT = "support_prompt_dismissal_count"
const val SUPPORT_PROMPT_NEXT_TYPE = "support_prompt_next_type"
const val WAS_RATE_PROMPT_ACCEPTED = "was_rate_prompt_accepted"
const val LAST_VERSION = "last_version"
const val SD_TREE_URI = "tree_uri_2"
const val PRIMARY_ANDROID_DATA_TREE_URI = "primary_android_data_tree_uri_2"
Expand Down
3 changes: 2 additions & 1 deletion commons/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -921,6 +921,7 @@
<string name="share_text">Hey, come check out %1$s at %2$s</string>
<string name="invite_via">Invite via</string>
<string name="rate_this_app">Rate this app</string>
<string name="rate_app_prompt">Enjoying the app? Please rate it on Google Play.</string>
<string name="donate">Donate</string>
<string name="follow_us">Follow us</string>
<string name="copyright">v %1$s\nCopyright © Fossify %2$d</string>
Expand All @@ -943,7 +944,7 @@
]]>
</string>
<string name="donate_new">Hello,&lt;br&gt;&lt;br&gt;hope you are enjoying the app. It contains no ads and we aren\'t collecting your data either, please support its development by purchasing &lt;a href=https://play.google.com/store/apps/details?id=org.fossify.thankyou&gt;Fossify Thank You&lt;/a&gt;. You will also have all app features including color customization unlocked.&lt;br&gt;&lt;br&gt;Thank you!</string>
<string name="donate_short">Support us by purchasing &lt;a href=https://play.google.com/store/apps/details?id=org.fossify.thankyou&gt;Fossify Thank You&lt;/a&gt; please, it will also unlock all app features including color customization.</string>
<string name="donate_short">Support us by purchasing &lt;a href=https://play.google.com/store/apps/details?id=org.fossify.thankyou&gt;Fossify Thank You&lt;/a&gt;. It also enables some additional customization.</string>
<string name="purchase">Purchase</string>
<string name="update_thank_you">Please update Fossify Thank You to the latest version</string>
<string name="before_asking_question_read_faq">Before you ask a question, please check the app settings and read the Frequently Asked Questions first. Maybe the solution is there.</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,15 @@ package org.fossify.commons.samples.activities
import android.content.Intent
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import org.fossify.commons.activities.BaseSimpleActivity
import org.fossify.commons.activities.ManageBlockedNumbersActivity
import org.fossify.commons.compose.alert_dialog.AlertDialogState
import org.fossify.commons.compose.alert_dialog.rememberAlertDialogState
import org.fossify.commons.compose.extensions.DEVELOPER_PLAY_STORE_URL
import org.fossify.commons.compose.extensions.FAKE_VERSION_APP_LABEL
import org.fossify.commons.compose.extensions.appLaunchedCompose
import org.fossify.commons.compose.extensions.enableEdgeToEdgeSimple
import org.fossify.commons.compose.extensions.onEventValue
import org.fossify.commons.compose.theme.AppThemeSurface
import org.fossify.commons.dialogs.ConfirmationDialog
import org.fossify.commons.dialogs.DonateAlertDialog
import org.fossify.commons.extensions.appLaunched
import org.fossify.commons.extensions.launchMoreAppsFromUsIntent
import org.fossify.commons.extensions.launchViewIntent
import org.fossify.commons.helpers.LICENSE_AUTOFITTEXTVIEW
Expand All @@ -30,7 +24,7 @@ class MainActivity : BaseSimpleActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
appLaunched(BuildConfig.APPLICATION_ID)
appLaunchedCompose(BuildConfig.APPLICATION_ID)
enableEdgeToEdgeSimple()
setContent {
AppThemeSurface {
Expand Down Expand Up @@ -58,32 +52,10 @@ class MainActivity : BaseSimpleActivity() {
openAbout = ::launchAbout,
moreAppsFromUs = ::launchMoreAppsFromUsIntent
)
AppLaunched()
}
}
}

@Composable
private fun AppLaunched(
donateAlertDialogState: AlertDialogState = getDonateAlertDialogState(),
) {
LaunchedEffect(Unit) {
appLaunchedCompose(
appId = BuildConfig.APPLICATION_ID,
showDonateDialog = donateAlertDialogState::show,
showUpgradeDialog = {}
)
}
}

@Composable
private fun getDonateAlertDialogState() =
rememberAlertDialogState().apply {
DialogMember {
DonateAlertDialog(alertDialogState = this)
}
}

private fun launchAbout() {
val licenses = LICENSE_AUTOFITTEXTVIEW

Expand Down
Loading