diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/AboutLinks.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/AboutLinks.kt index 6165f77..20b53cc 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/AboutLinks.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/AboutLinks.kt @@ -1,8 +1,11 @@ package app.seb3thehacker.gearslip.ui +import android.content.ClipData +import android.content.ClipboardManager import android.content.Context import android.content.Intent import android.net.Uri +import android.widget.Toast import androidx.compose.foundation.clickable import androidx.compose.material.icons.Icons 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. */ internal object GearslipLinks { 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 SOURCE = "https://github.com/Seb3thehacker/gearslip" 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)) } } + + /** 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 internal fun DonateRows() { val context = LocalContext.current @@ -43,10 +61,30 @@ internal fun DonateRows() { SettingsRow( "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, 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. */ diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/DonatePrompt.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/DonatePrompt.kt index d7f63ed..25c1c9e 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/DonatePrompt.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/DonatePrompt.kt @@ -9,6 +9,7 @@ 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 @@ -104,6 +105,11 @@ internal fun DonateDialog(live: Boolean) { Button(onClick = { give(GearslipLinks.LIBERAPAY) }, Modifier.fillMaxWidth()) { 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()) { Text("Not now") } diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/IntroScreen.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/IntroScreen.kt index eb6df3b..338517a 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/IntroScreen.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/IntroScreen.kt @@ -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, action = "See which cars work" to { GearslipLinks.open(context, GearslipLinks.CARS) }, outlined = true, + secondAction = "Support on Liberapay" to { GearslipLinks.open(context, GearslipLinks.LIBERAPAY) }, ) { HeartIcon(c.onTertiaryC) } 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 " + @@ -396,6 +397,7 @@ private fun BadgeStep( c: IntroColors, cq: (Float) -> Dp, cqSp: (Float) -> TextUnit, badge: Color, title: String, body: String, key: Any, action: Pair Unit>? = null, outlined: Boolean = false, + secondAction: Pair Unit>? = null, extra: (@Composable () -> Unit)? = null, icon: @Composable () -> Unit, ) { @@ -423,15 +425,14 @@ private fun BadgeStep( modifier = Modifier.up(entrance(key, 350, 700), cq(7f)), ) extra?.invoke() - if (action != null) { - val (label, onClick) = action - Spacer(Modifier.height(if (outlined) cq(7f) else cq(9f))) + listOfNotNull(action, secondAction).forEachIndexed { i, (label, onClick) -> + Spacer(Modifier.height(if (i > 0) cq(3f) else if (outlined) cq(7f) else cq(9f))) Text( label, color = if (outlined) c.primary else c.onPrimary, style = TextStyle(fontSize = cqSp(if (outlined) 4.4f else 4.6f), fontWeight = FontWeight.Bold), modifier = Modifier - .up(entrance(key, 500, 700), cq(7f)) + .up(entrance(key, 500 + i * 150, 700), cq(7f)) .clip(RoundedCornerShape(50)) .then( if (outlined) Modifier.border(BorderStroke(2.dp, c.outline), RoundedCornerShape(50))