mirror of
https://github.com/twinksoftware/Gearslip.git
synced 2026-11-10 16:02:25 +01:00
donations: at the top of Settings, and a popup on Home after the third drive that worked, then at most every three months
This commit is contained in:
1 parent
71397a0777
commit
3042e01e75
4 files changed
+124
-4
No files matched your search
@@ -228,6 +228,7 @@ object SessionReport {
|
||||
}
|
||||
if (outcome == "connected") runCatching {
|
||||
app.seb3thehacker.gearslip.stats.WorkedPrompt.onDriveWorked(context, headUnitInfo, protocolVersion, carScreen, carDpi)
|
||||
app.seb3thehacker.gearslip.ui.DonatePrompt.onDriveWorked(context)
|
||||
}
|
||||
runCatching { app.seb3thehacker.gearslip.update.UpdateChecker.onDriveEnded(context) }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
package app.seb3thehacker.gearslip.ui
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Favorite
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import app.seb3thehacker.gearslip.GearslipLog
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
/**
|
||||
* The donation ask on Home, after a drive that worked. A driver sees it first after their third
|
||||
* such drive, so Gearslip has earned it, and then at most once every three months. Tapping either
|
||||
* donate button ends it for good.
|
||||
*/
|
||||
object DonatePrompt {
|
||||
|
||||
private const val PREFS = "donate_prompt"
|
||||
private const val KEY_DRIVES = "drives"
|
||||
private const val KEY_LAST = "last_shown"
|
||||
private const val KEY_DONATED = "donated"
|
||||
private const val KEY_WAITING = "waiting"
|
||||
private const val FIRST_AFTER = 3
|
||||
private const val GAP_MS = 90L * 24 * 60 * 60 * 1000
|
||||
|
||||
private fun prefs(context: Context) = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
||||
|
||||
private var loaded = false
|
||||
private val flow = MutableStateFlow(false)
|
||||
|
||||
/** True while the ask is waiting for the driver to open Home. */
|
||||
fun pending(context: Context): StateFlow<Boolean> {
|
||||
if (!loaded) {
|
||||
loaded = true
|
||||
flow.value = prefs(context).getBoolean(KEY_WAITING, false)
|
||||
}
|
||||
return flow
|
||||
}
|
||||
|
||||
/** A drive that reached the car's screen ended cleanly. Asks if it's time. */
|
||||
fun onDriveWorked(context: Context) {
|
||||
val p = prefs(context)
|
||||
if (p.getBoolean(KEY_DONATED, false)) return
|
||||
val drives = p.getInt(KEY_DRIVES, 0) + 1
|
||||
val due = drives >= FIRST_AFTER && System.currentTimeMillis() - p.getLong(KEY_LAST, 0L) > GAP_MS
|
||||
p.edit().putInt(KEY_DRIVES, drives).apply {
|
||||
if (due) putBoolean(KEY_WAITING, true)
|
||||
}.apply()
|
||||
if (due) {
|
||||
loaded = true
|
||||
flow.value = true
|
||||
}
|
||||
}
|
||||
|
||||
/** The driver closed the ask; [donated] if they picked a way to give. */
|
||||
fun answer(context: Context, donated: Boolean) {
|
||||
prefs(context).edit()
|
||||
.putBoolean(KEY_WAITING, false)
|
||||
.putLong(KEY_LAST, System.currentTimeMillis())
|
||||
.apply { if (donated) putBoolean(KEY_DONATED, true) }
|
||||
.apply()
|
||||
loaded = true
|
||||
flow.value = false
|
||||
GearslipLog.i(if (donated) "donate prompt: opened a donation link" else "donate prompt: not now")
|
||||
}
|
||||
}
|
||||
|
||||
/** Shows the donation ask over Home when one is waiting and the car isn't connected. */
|
||||
@Composable
|
||||
internal fun DonateDialog(live: Boolean) {
|
||||
val context = LocalContext.current
|
||||
val waiting by remember { DonatePrompt.pending(context) }.collectAsStateWithLifecycle()
|
||||
if (!waiting || live) return
|
||||
fun give(url: String) {
|
||||
DonatePrompt.answer(context, donated = true)
|
||||
GearslipLinks.open(context, url)
|
||||
}
|
||||
AlertDialog(
|
||||
onDismissRequest = { DonatePrompt.answer(context, donated = false) },
|
||||
icon = { Icon(Icons.Filled.Favorite, contentDescription = null) },
|
||||
title = { Text("Glad that drive worked") },
|
||||
text = {
|
||||
Text(
|
||||
"Gearslip is free, with no ads and no tracking. If it gets you on the road, a " +
|
||||
"donation keeps it going.",
|
||||
)
|
||||
},
|
||||
confirmButton = {
|
||||
Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Button(onClick = { give(GearslipLinks.SPONSORS) }, Modifier.fillMaxWidth()) {
|
||||
Text("Donate on GitHub")
|
||||
}
|
||||
OutlinedButton(onClick = { give(GearslipLinks.LIBERAPAY) }, Modifier.fillMaxWidth()) {
|
||||
Text("Donate on Liberapay")
|
||||
}
|
||||
TextButton(onClick = { DonatePrompt.answer(context, donated = false) }, Modifier.fillMaxWidth()) {
|
||||
Text("Not now")
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -144,6 +144,8 @@ fun HomeScreen(
|
||||
}
|
||||
// Sharing just went on from the "it worked" ask: a little thanks.
|
||||
if (celebrate) ConfettiFall(onDone = WorkedPrompt::celebrated)
|
||||
// The usage notes ask comes first; the donation ask waits until that's answered.
|
||||
if (workedCar == null) DonateDialog(live)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -119,6 +119,10 @@ fun SettingsScreen(
|
||||
verticalArrangement = Arrangement.spacedBy(24.dp),
|
||||
) {
|
||||
Spacer(Modifier.height(0.dp))
|
||||
SettingsSection(
|
||||
"Support Gearslip",
|
||||
footer = "Gearslip is free, with no ads and no tracking. Donations keep it going.",
|
||||
) { DonateRows() }
|
||||
ConnectionSection()
|
||||
HelpSection(onOpenHelp, onOpenCarPreview, onOpenLogs)
|
||||
CarAudioSection()
|
||||
@@ -131,10 +135,6 @@ fun SettingsScreen(
|
||||
) { UsageStatsRows() }
|
||||
SettingsSection("Updates") { UpdateRow() }
|
||||
CertificateSection()
|
||||
SettingsSection(
|
||||
"Support Gearslip",
|
||||
footer = "Gearslip is free, with no ads and no tracking. Donations keep it going.",
|
||||
) { DonateRows() }
|
||||
AboutSection(onOpenWhatsNew, onReplayTutorial, onReplayIntro)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user