support: a Liberapay button on the intro's thank-you page, no "through a nonprofit", and room for Monero and a give-once card link, hidden until they're set

This commit is contained in:
Seb3thehacker committed 2026-10-14 19:21:41 -07:00
1 parent 35bd239c2f
commit 879b428d1f
3 files changed
+51 -6

No files matched your search

@@ -1,8 +1,11 @@
package app.seb3thehacker.gearslip.ui package app.seb3thehacker.gearslip.ui
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.widget.Toast
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
@@ -18,6 +21,10 @@ import androidx.compose.ui.platform.LocalContext
/** Where Gearslip lives outside the app: support, the chat room, and the code. */ /** Where Gearslip lives outside the app: support, the chat room, and the code. */
internal object GearslipLinks { internal object GearslipLinks {
const val LIBERAPAY = "https://liberapay.com/Seb3thehacker" const val LIBERAPAY = "https://liberapay.com/Seb3thehacker"
/** A card payment page for giving once, any amount. Its row stays hidden while this is empty. */
const val PAYMENT_LINK = ""
/** Gearslip's Monero address. Its row stays hidden while this is empty. */
const val MONERO = ""
const val MATRIX = "https://matrix.to/#/!mf0_mgBd5KqKpc9FfX_wHp1jFCcwAtPrndIn_oGIyMk?via=matrix.org" const val MATRIX = "https://matrix.to/#/!mf0_mgBd5KqKpc9FfX_wHp1jFCcwAtPrndIn_oGIyMk?via=matrix.org"
const val SOURCE = "https://github.com/Seb3thehacker/gearslip" const val SOURCE = "https://github.com/Seb3thehacker/gearslip"
const val CARS = "$SOURCE/blob/main/CARS.md" const val CARS = "$SOURCE/blob/main/CARS.md"
@@ -27,9 +34,20 @@ internal object GearslipLinks {
context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)) context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
} }
} }
/** Opens a Monero wallet on the address, or copies it when no wallet app is installed. */
fun openMonero(context: Context) {
val opened = runCatching {
context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("monero:$MONERO")).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
}.isSuccess
if (opened) return
context.getSystemService(ClipboardManager::class.java)
?.setPrimaryClip(ClipData.newPlainText("Monero address", MONERO))
Toast.makeText(context, "Monero address copied", Toast.LENGTH_SHORT).show()
}
} }
/** The way to support Gearslip, opening in the browser. */ /** The ways to support Gearslip. Liberapay always; card and Monero once they're set up. */
@Composable @Composable
internal fun DonateRows() { internal fun DonateRows() {
val context = LocalContext.current val context = LocalContext.current
@@ -43,10 +61,30 @@ internal fun DonateRows() {
SettingsRow( SettingsRow(
"Liberapay", "Liberapay",
Modifier.clickable { GearslipLinks.open(context, GearslipLinks.LIBERAPAY) }, Modifier.clickable { GearslipLinks.open(context, GearslipLinks.LIBERAPAY) },
subtitle = "Give weekly or monthly, through a nonprofit", subtitle = "Give weekly or monthly",
icon = Icons.Filled.Favorite, icon = Icons.Filled.Favorite,
trailing = arrow, trailing = arrow,
) )
if (GearslipLinks.PAYMENT_LINK.isNotEmpty()) {
SettingsDivider()
SettingsRow(
"Give once",
Modifier.clickable { GearslipLinks.open(context, GearslipLinks.PAYMENT_LINK) },
subtitle = "Any amount, by card",
icon = Icons.Filled.Favorite,
trailing = arrow,
)
}
if (GearslipLinks.MONERO.isNotEmpty()) {
SettingsDivider()
SettingsRow(
"Monero",
Modifier.clickable { GearslipLinks.openMonero(context) },
subtitle = "Opens your wallet, or copies the address",
icon = Icons.Filled.Favorite,
trailing = arrow,
)
}
} }
/** The About section's links: the chat room, the source code, and credits. */ /** The About section's links: the chat room, the source code, and credits. */
@@ -9,6 +9,7 @@ import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -104,6 +105,11 @@ internal fun DonateDialog(live: Boolean) {
Button(onClick = { give(GearslipLinks.LIBERAPAY) }, Modifier.fillMaxWidth()) { Button(onClick = { give(GearslipLinks.LIBERAPAY) }, Modifier.fillMaxWidth()) {
Text("Support on Liberapay") Text("Support on Liberapay")
} }
if (GearslipLinks.PAYMENT_LINK.isNotEmpty()) {
OutlinedButton(onClick = { give(GearslipLinks.PAYMENT_LINK) }, Modifier.fillMaxWidth()) {
Text("Give once by card")
}
}
TextButton(onClick = { DonatePrompt.answer(context, donated = false) }, Modifier.fillMaxWidth()) { TextButton(onClick = { DonatePrompt.answer(context, donated = false) }, Modifier.fillMaxWidth()) {
Text("Not now") Text("Not now")
} }
@@ -228,6 +228,7 @@ fun IntroScreen(onDone: () -> Unit) {
"Each note shows which cars work and which still fail, so I know what to fix next.", step, "Each note shows which cars work and which still fail, so I know what to fix next.", step,
action = "See which cars work" to { GearslipLinks.open(context, GearslipLinks.CARS) }, action = "See which cars work" to { GearslipLinks.open(context, GearslipLinks.CARS) },
outlined = true, outlined = true,
secondAction = "Support on Liberapay" to { GearslipLinks.open(context, GearslipLinks.LIBERAPAY) },
) { HeartIcon(c.onTertiaryC) } ) { HeartIcon(c.onTertiaryC) }
IntroStep.CHAT -> BadgeStep(c, ::cq, ::cqSp, c.primaryC, "Come say hi.", IntroStep.CHAT -> BadgeStep(c, ::cq, ::cqSp, c.primaryC, "Come say hi.",
"Gearslip has a chat room on Matrix. Talk with other drivers about cars, apps, and " + "Gearslip has a chat room on Matrix. Talk with other drivers about cars, apps, and " +
@@ -396,6 +397,7 @@ private fun BadgeStep(
c: IntroColors, cq: (Float) -> Dp, cqSp: (Float) -> TextUnit, badge: Color, c: IntroColors, cq: (Float) -> Dp, cqSp: (Float) -> TextUnit, badge: Color,
title: String, body: String, key: Any, title: String, body: String, key: Any,
action: Pair<String, () -> Unit>? = null, outlined: Boolean = false, action: Pair<String, () -> Unit>? = null, outlined: Boolean = false,
secondAction: Pair<String, () -> Unit>? = null,
extra: (@Composable () -> Unit)? = null, extra: (@Composable () -> Unit)? = null,
icon: @Composable () -> Unit, icon: @Composable () -> Unit,
) { ) {
@@ -423,15 +425,14 @@ private fun BadgeStep(
modifier = Modifier.up(entrance(key, 350, 700), cq(7f)), modifier = Modifier.up(entrance(key, 350, 700), cq(7f)),
) )
extra?.invoke() extra?.invoke()
if (action != null) { listOfNotNull(action, secondAction).forEachIndexed { i, (label, onClick) ->
val (label, onClick) = action Spacer(Modifier.height(if (i > 0) cq(3f) else if (outlined) cq(7f) else cq(9f)))
Spacer(Modifier.height(if (outlined) cq(7f) else cq(9f)))
Text( Text(
label, label,
color = if (outlined) c.primary else c.onPrimary, color = if (outlined) c.primary else c.onPrimary,
style = TextStyle(fontSize = cqSp(if (outlined) 4.4f else 4.6f), fontWeight = FontWeight.Bold), style = TextStyle(fontSize = cqSp(if (outlined) 4.4f else 4.6f), fontWeight = FontWeight.Bold),
modifier = Modifier modifier = Modifier
.up(entrance(key, 500, 700), cq(7f)) .up(entrance(key, 500 + i * 150, 700), cq(7f))
.clip(RoundedCornerShape(50)) .clip(RoundedCornerShape(50))
.then( .then(
if (outlined) Modifier.border(BorderStroke(2.dp, c.outline), RoundedCornerShape(50)) if (outlined) Modifier.border(BorderStroke(2.dp, c.outline), RoundedCornerShape(50))