Add a one time popup asking for donation from long time users (#3203)

Also remove the empty "Updated to X.Y.Z" popup after app update
This commit is contained in:
AntsyLich
2026-04-10 02:08:20 +06:00
committed by GitHub
parent 02c6427823
commit e4b7002909
6 changed files with 172 additions and 33 deletions
@@ -46,4 +46,9 @@ class BasePreferences(
)
val installationId: Preference<String> = preferenceStore.getString(Preference.appStateKey("installation_id"), "")
val donationCampaignShown: Preference<Boolean> = preferenceStore.getBoolean(
Preference.appStateKey("donation_campaign_shown"),
false,
)
}
@@ -28,7 +28,7 @@ fun NavigatorAdaptiveSheet(
content = { sheetNavigator ->
AdaptiveSheet(
onDismissRequest = onDismissRequest,
enableSwipeDismiss = enableSwipeDismiss(sheetNavigator),
enableImplicitDismiss = enableSwipeDismiss(sheetNavigator),
) {
ScreenTransition(
navigator = sheetNavigator,
@@ -63,7 +63,7 @@ fun NavigatorAdaptiveSheet(
fun AdaptiveSheet(
onDismissRequest: () -> Unit,
modifier: Modifier = Modifier,
enableSwipeDismiss: Boolean = true,
enableImplicitDismiss: Boolean = true,
content: @Composable () -> Unit,
) {
val isTabletUi = isTabletUi()
@@ -74,7 +74,7 @@ fun AdaptiveSheet(
) {
AdaptiveSheetImpl(
isTabletUi = isTabletUi,
enableSwipeDismiss = enableSwipeDismiss,
enableImplicitDismiss = enableImplicitDismiss,
onDismissRequest = onDismissRequest,
modifier = modifier,
) {
@@ -5,6 +5,7 @@ import android.app.SearchManager
import android.app.assist.AssistContent
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Color
import android.os.Build
import android.os.Bundle
@@ -14,21 +15,32 @@ import androidx.activity.SystemBarStyle
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.windowInsetsBottomHeight
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.AlertDialog
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.OpenInNew
import androidx.compose.material.icons.filled.VolunteerActivism
import androidx.compose.material3.Button
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
@@ -41,6 +53,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.unit.dp
import androidx.core.animation.doOnEnd
import androidx.core.net.toUri
import androidx.core.splashscreen.SplashScreen
@@ -55,6 +69,7 @@ import cafe.adriel.voyager.navigator.NavigatorDisposeBehavior
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.source.interactor.GetIncognitoState
import eu.kanade.presentation.components.AdaptiveSheet
import eu.kanade.presentation.components.AppStateBanners
import eu.kanade.presentation.components.DownloadedOnlyBannerBackgroundColor
import eu.kanade.presentation.components.IncognitoModeBannerBackgroundColor
@@ -63,12 +78,10 @@ import eu.kanade.presentation.more.settings.screen.browse.ExtensionReposScreen
import eu.kanade.presentation.more.settings.screen.data.RestoreBackupScreen
import eu.kanade.presentation.util.AssistContentScreen
import eu.kanade.presentation.util.DefaultNavigatorScreenTransition
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.data.cache.ChapterCache
import eu.kanade.tachiyomi.data.download.DownloadCache
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
import eu.kanade.tachiyomi.data.updater.AppUpdateChecker
import eu.kanade.tachiyomi.data.updater.RELEASE_URL
import eu.kanade.tachiyomi.extension.api.ExtensionApi
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen
@@ -80,7 +93,6 @@ import eu.kanade.tachiyomi.ui.more.NewUpdateScreen
import eu.kanade.tachiyomi.ui.more.OnboardingScreen
import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.isNavigationBarNeedsScrim
import eu.kanade.tachiyomi.util.system.openInBrowser
import eu.kanade.tachiyomi.util.system.updaterEnabled
import eu.kanade.tachiyomi.util.view.setComposeContent
import kotlinx.coroutines.channels.awaitClose
@@ -93,6 +105,7 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import logcat.LogPriority
import mihon.core.migration.Migrator
import mihon.feature.support.SupportUsScreen
import tachiyomi.core.common.Constants
import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.core.common.util.system.logcat
@@ -100,9 +113,14 @@ import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.release.interactor.GetApplicationRelease
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.injectLazy
import kotlin.time.Clock
import kotlin.time.Duration.Companion.days
import kotlin.time.Instant
import kotlin.time.times
class MainActivity : BaseActivity() {
@@ -131,7 +149,7 @@ class MainActivity : BaseActivity() {
super.onCreate(savedInstanceState)
val didMigration = Migrator.awaitAndRelease()
Migrator.awaitAndRelease()
// Do not let the launcher create a new activity http://stackoverflow.com/questions/16283079
if (!isTaskRoot) {
@@ -240,24 +258,7 @@ class MainActivity : BaseActivity() {
CheckForUpdates()
ShowOnboarding()
}
var showChangelog by remember { mutableStateOf(didMigration && !BuildConfig.DEBUG) }
if (showChangelog) {
AlertDialog(
onDismissRequest = { showChangelog = false },
title = { Text(text = stringResource(MR.strings.updated_version, BuildConfig.VERSION_NAME)) },
dismissButton = {
TextButton(onClick = { openInBrowser(RELEASE_URL) }) {
Text(text = stringResource(MR.strings.whats_new))
}
},
confirmButton = {
TextButton(onClick = { showChangelog = false }) {
Text(text = stringResource(MR.strings.action_ok))
}
},
)
ShowDonationCampaign()
}
}
@@ -343,6 +344,128 @@ class MainActivity : BaseActivity() {
}
}
@Composable
private fun ShowDonationCampaign() {
val navigator = LocalNavigator.currentOrThrow
var showCampaign by remember { mutableStateOf(false) }
if (showCampaign) {
val uriHandler = LocalUriHandler.current
val dismissSupportMessage = {
preferences.donationCampaignShown.set(true)
@Suppress("AssignedValueIsNeverRead")
showCampaign = false
}
AdaptiveSheet(
onDismissRequest = dismissSupportMessage,
enableImplicitDismiss = false,
) {
Column {
Spacer(modifier = Modifier.height(16.dp))
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.padding(16.dp)
.weight(1f, fill = false)
.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
Text(
text = stringResource(MR.strings.donationCampaign_title),
color = MaterialTheme.colorScheme.primary,
style = MaterialTheme.typography.headlineSmall,
)
Text(
text = stringResource(MR.strings.donationCampaign_paragraph1),
style = MaterialTheme.typography.bodyMedium,
)
Text(
text = stringResource(MR.strings.donationCampaign_paragraph2),
style = MaterialTheme.typography.bodyMedium,
)
Text(
text = stringResource(MR.strings.donationCampaign_paragraph3),
style = MaterialTheme.typography.bodyMedium,
)
}
HorizontalDivider()
Button(
modifier = Modifier
.padding(top = MaterialTheme.padding.small)
.padding(horizontal = MaterialTheme.padding.medium)
.fillMaxWidth(),
onClick = {
navigator.push(SupportUsScreen())
dismissSupportMessage()
},
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
) {
Icon(
imageVector = Icons.Default.VolunteerActivism,
contentDescription = null,
)
Text(
text = stringResource(MR.strings.label_support_us),
color = MaterialTheme.colorScheme.onPrimary,
)
}
}
Row(
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
modifier = Modifier
.padding(bottom = MaterialTheme.padding.small)
.padding(horizontal = MaterialTheme.padding.medium),
) {
OutlinedButton(
modifier = Modifier
.fillMaxWidth()
.weight(1f),
onClick = { uriHandler.openUri(Constants.URL_DISCORD) },
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
) {
Text(
text = stringResource(MR.strings.donationCampaign_contactPlatform),
)
Icon(
imageVector = Icons.AutoMirrored.Default.OpenInNew,
contentDescription = null,
)
}
}
OutlinedButton(
modifier = Modifier
.fillMaxWidth()
.weight(1f),
onClick = dismissSupportMessage,
) {
Text(
text = stringResource(MR.strings.donationCampaign_dismiss),
)
}
}
}
}
}
LaunchedEffect(Unit) {
try {
val firstInstallTime = packageManager.getPackageInfo(packageName, 0).firstInstallTime
val eligibleTime = Instant.fromEpochMilliseconds(firstInstallTime).plus(6 * 30.days)
@Suppress("AssignedValueIsNeverRead")
showCampaign = (Clock.System.now() >= eligibleTime && !preferences.donationCampaignShown.get())
} catch (_: PackageManager.NameNotFoundException) {
}
}
}
/**
* Sets custom splash screen exit animation on devices prior to Android 12.
*