mirror of
https://github.com/twinksoftware/Gearslip.git
synced 2026-11-10 16:02:25 +01:00
intro: what's new in 0.3.12, and setup always ends on recommended apps
A fresh look, directions at a glance, connecting by itself, better calls and settings up front, then app picks with an Other cool apps row and the button picker. The intro is named after its release, so a patch can carry one.
This commit is contained in:
1 parent
6751951308
commit
8d673d855b
3 files changed
+50
-45
No files matched your search
@@ -81,6 +81,11 @@ import androidx.compose.foundation.layout.PaddingValues
|
|||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.PlayArrow
|
import androidx.compose.material.icons.filled.PlayArrow
|
||||||
import androidx.compose.material.icons.filled.Search
|
import androidx.compose.material.icons.filled.Search
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material.icons.filled.Settings
|
||||||
|
import androidx.compose.material.icons.filled.Call
|
||||||
|
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
||||||
|
import androidx.compose.material.icons.filled.Star
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import app.seb3thehacker.gearslip.car.CarSettings
|
import app.seb3thehacker.gearslip.car.CarSettings
|
||||||
import app.seb3thehacker.gearslip.car.theme.GsButton
|
import app.seb3thehacker.gearslip.car.theme.GsButton
|
||||||
@@ -108,8 +113,11 @@ object Intro {
|
|||||||
private const val KEY_SEEN = "seen_release"
|
private const val KEY_SEEN = "seen_release"
|
||||||
private const val KEY_STEP = "step"
|
private const val KEY_STEP = "step"
|
||||||
|
|
||||||
/** "0.3" from "0.3.00-alpha": the intro belongs to a release, not to each patch. */
|
/**
|
||||||
val release: String = BuildConfig.VERSION_NAME.split('.').take(2).joinToString(".")
|
* The release this intro is about. It's named here rather than taken from the version, so a
|
||||||
|
* patch release can carry an intro of its own: anyone who saw an earlier one sees this once.
|
||||||
|
*/
|
||||||
|
const val release: String = "0.3.12"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True once per release for people who used Gearslip before. A new install, or one with its
|
* True once per release for people who used Gearslip before. A new install, or one with its
|
||||||
@@ -163,7 +171,7 @@ internal fun introColors(): IntroColors {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** In the order shown: what's new, what to pick, housekeeping, then the people behind it. */
|
/** In the order shown: what's new, what to pick, housekeeping, then the people behind it. */
|
||||||
private enum class IntroStep { WELCOME, WIRELESS, KNOBS, VOICE, THEME, APPS, GRAPHENE, CHAT, THANKS }
|
private enum class IntroStep { WELCOME, LOOK, DIRECTIONS, CONNECTS, CALLS, SETTINGS, APPS, THEME }
|
||||||
|
|
||||||
private val Pop = CubicBezierEasing(0.3f, 1.6f, 0.5f, 1f)
|
private val Pop = CubicBezierEasing(0.3f, 1.6f, 0.5f, 1f)
|
||||||
private val Rise = CubicBezierEasing(0.2f, 0.8f, 0.2f, 1f)
|
private val Rise = CubicBezierEasing(0.2f, 0.8f, 0.2f, 1f)
|
||||||
@@ -172,10 +180,7 @@ private val Rise = CubicBezierEasing(0.2f, 0.8f, 0.2f, 1f)
|
|||||||
fun IntroScreen(onDone: () -> Unit) {
|
fun IntroScreen(onDone: () -> Unit) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val c = introColors()
|
val c = introColors()
|
||||||
// The GrapheneOS screen is only news to people running it.
|
val steps = remember { IntroStep.entries.toList() }
|
||||||
val steps = remember {
|
|
||||||
IntroStep.entries.filter { it != IntroStep.GRAPHENE || UsageStats.osName(context) == "GrapheneOS" }
|
|
||||||
}
|
|
||||||
// Turning exploit protection back on restarts the app, so the intro picks up where it was.
|
// Turning exploit protection back on restarts the app, so the intro picks up where it was.
|
||||||
var index by remember {
|
var index by remember {
|
||||||
mutableIntStateOf(steps.indexOfFirst { it.name == Intro.savedStep(context) }.coerceAtLeast(0))
|
mutableIntStateOf(steps.indexOfFirst { it.name == Intro.savedStep(context) }.coerceAtLeast(0))
|
||||||
@@ -201,36 +206,26 @@ fun IntroScreen(onDone: () -> Unit) {
|
|||||||
Box(Modifier.weight(1f).fillMaxWidth()) {
|
Box(Modifier.weight(1f).fillMaxWidth()) {
|
||||||
when (step) {
|
when (step) {
|
||||||
IntroStep.WELCOME -> Welcome(c, ::cq, ::cqSp, h)
|
IntroStep.WELCOME -> Welcome(c, ::cq, ::cqSp, h)
|
||||||
IntroStep.WIRELESS -> BadgeStep(c, ::cq, ::cqSp, c.secondaryC, "Wireless.",
|
IntroStep.LOOK -> BadgeStep(c, ::cq, ::cqSp, c.primaryC, "A fresh look.",
|
||||||
"If your car does wireless Android Auto, pair once and leave the cable at home.", step) { WifiIcon(c.onSecondaryC) }
|
"The car screen takes your phone's colors, and every card and button matches.", step) {
|
||||||
IntroStep.KNOBS -> BadgeStep(c, ::cq, ::cqSp, c.tertiaryC, "Knobs work.",
|
GlyphIcon(Icons.Filled.Star, c.onPrimaryC)
|
||||||
"Turn, push, done. No touchscreen? Gearslip doesn't need one anymore.", step) { DialIcon(c.onTertiaryC) }
|
}
|
||||||
IntroStep.VOICE -> VoiceStep(c, ::cq, ::cqSp, step)
|
IntroStep.DIRECTIONS -> BadgeStep(c, ::cq, ::cqSp, c.secondaryC, "Directions at a glance.",
|
||||||
IntroStep.GRAPHENE -> BadgeStep(c, ::cq, ::cqSp, c.secondaryC, "Protections back on.",
|
"A big turn card on the left shows the arrow, the distance, and your lane. The map keeps the right side.", step) {
|
||||||
"On GrapheneOS, Gearslip no longer needs exploit protection compatibility mode. If you " +
|
GlyphIcon(Icons.Filled.KeyboardArrowUp, c.onSecondaryC)
|
||||||
"turned it on for Gearslip, turn it off to get full protection back.", step,
|
}
|
||||||
action = "Open app settings" to {
|
IntroStep.CONNECTS -> BadgeStep(c, ::cq, ::cqSp, c.tertiaryC, "Connects by itself.",
|
||||||
runCatching {
|
"Say yes once after a wireless drive, and Gearslip connects whenever the car's Bluetooth does.", step) {
|
||||||
context.startActivity(
|
WifiIcon(c.onTertiaryC)
|
||||||
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts("package", context.packageName, null)),
|
}
|
||||||
)
|
IntroStep.CALLS -> BadgeStep(c, ::cq, ::cqSp, c.primaryC, "Better calls.",
|
||||||
|
"Dial from the car, then mute or hang up from the nav bar.", step) {
|
||||||
|
GlyphIcon(Icons.Filled.Call, c.onPrimaryC)
|
||||||
|
}
|
||||||
|
IntroStep.SETTINGS -> BadgeStep(c, ::cq, ::cqSp, c.secondaryC, "Settings up front.",
|
||||||
|
"Change the car's settings from the phone's home screen, before you drive.", step) {
|
||||||
|
GlyphIcon(Icons.Filled.Settings, c.onSecondaryC)
|
||||||
}
|
}
|
||||||
},
|
|
||||||
outlined = true,
|
|
||||||
) { ShieldIcon(c.onSecondaryC, c.secondaryC) }
|
|
||||||
IntroStep.THANKS -> BadgeStep(c, ::cq, ::cqSp, c.tertiaryC, "Thank you.",
|
|
||||||
"Thanks to your usage notes, Gearslip is confirmed to work in cars newer than 2020. " +
|
|
||||||
"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 " +
|
|
||||||
"where Gearslip is headed.", step,
|
|
||||||
action = "Join on Matrix" to { GearslipLinks.open(context, GearslipLinks.MATRIX) },
|
|
||||||
outlined = true,
|
|
||||||
) { ChatIcon(c.onPrimaryC, c.primaryC) }
|
|
||||||
IntroStep.APPS -> BadgeStep(c, ::cq, ::cqSp, c.secondaryC, "Good company.",
|
IntroStep.APPS -> BadgeStep(c, ::cq, ::cqSp, c.secondaryC, "Good company.",
|
||||||
"Free, open-source apps that work well in the car. The first in each row works best. Tap one to see it on GitHub.", step,
|
"Free, open-source apps that work well in the car. The first in each row works best. Tap one to see it on GitHub.", step,
|
||||||
extra = { AppPicks(c, ::cq, ::cqSp, step) }) { StarIcon(c.onSecondaryC) }
|
extra = { AppPicks(c, ::cq, ::cqSp, step) }) { StarIcon(c.onSecondaryC) }
|
||||||
@@ -299,7 +294,7 @@ private fun Welcome(c: IntroColors, cq: (Float) -> Dp, cqSp: (Float) -> TextUnit
|
|||||||
modifier = Modifier.offset(x = -cq(1.5f)).pop(entrance(key, 250, 900)),
|
modifier = Modifier.offset(x = -cq(1.5f)).pop(entrance(key, 250, 900)),
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
"alpha · the big one", color = c.onTertiaryC,
|
"alpha · a fresh look", color = c.onTertiaryC,
|
||||||
style = TextStyle(fontSize = cqSp(4f), fontWeight = FontWeight.Bold),
|
style = TextStyle(fontSize = cqSp(4f), fontWeight = FontWeight.Bold),
|
||||||
modifier = Modifier.padding(top = cq(4f)).up(entrance(key, 700), cq(7f))
|
modifier = Modifier.padding(top = cq(4f)).up(entrance(key, 700), cq(7f))
|
||||||
.clip(RoundedCornerShape(50)).background(c.tertiaryC).padding(horizontal = cq(4.4f), vertical = cq(2.2f)),
|
.clip(RoundedCornerShape(50)).background(c.tertiaryC).padding(horizontal = cq(4.4f), vertical = cq(2.2f)),
|
||||||
@@ -535,6 +530,9 @@ private val APP_PICKS = listOf(
|
|||||||
"Podcasts" to listOf(
|
"Podcasts" to listOf(
|
||||||
"AntennaPod" to "https://github.com/AntennaPod/AntennaPod",
|
"AntennaPod" to "https://github.com/AntennaPod/AntennaPod",
|
||||||
),
|
),
|
||||||
|
"Other cool apps" to listOf(
|
||||||
|
"RF Sentinel" to "https://github.com/CIS-C0/RFSentinel",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
/** One row per kind, with a chip per app opening its GitHub page; the rows arrive one after another. */
|
/** One row per kind, with a chip per app opening its GitHub page; the rows arrive one after another. */
|
||||||
@@ -673,6 +671,12 @@ private inline fun DrawScope.inIconBox(block: DrawScope.() -> Unit) {
|
|||||||
scale(s, s, Offset.Zero) { block() }
|
scale(s, s, Offset.Zero) { block() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** A Material glyph filling a badge, for steps without a drawing of their own. */
|
||||||
|
@Composable
|
||||||
|
private fun GlyphIcon(glyph: androidx.compose.ui.graphics.vector.ImageVector, color: Color) {
|
||||||
|
Icon(glyph, contentDescription = null, tint = color, modifier = Modifier.fillMaxSize())
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun WifiIcon(color: Color) {
|
internal fun WifiIcon(color: Color) {
|
||||||
val t = rememberInfiniteTransition(label = "wifi")
|
val t = rememberInfiniteTransition(label = "wifi")
|
||||||
@@ -773,7 +777,7 @@ internal fun StarIcon(color: Color) {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun ShieldIcon(color: Color, tick: Color) {
|
internal fun ShieldIcon(color: Color, tick: Color) {
|
||||||
val p = entrance(IntroStep.GRAPHENE, 800, 500)
|
val p = entrance("shield", 800, 500)
|
||||||
Canvas(Modifier.fillMaxSize()) {
|
Canvas(Modifier.fillMaxSize()) {
|
||||||
inIconBox {
|
inIconBox {
|
||||||
val shield = Path().apply {
|
val shield = Path().apply {
|
||||||
|
|||||||
@@ -57,6 +57,9 @@ private val recommended = listOf(
|
|||||||
"Podcasts" to listOf(
|
"Podcasts" to listOf(
|
||||||
RecommendedApp("AntennaPod", "de.danoeh.antennapod", "https://github.com/AntennaPod/AntennaPod", "Podcasts, chapters and all."),
|
RecommendedApp("AntennaPod", "de.danoeh.antennapod", "https://github.com/AntennaPod/AntennaPod", "Podcasts, chapters and all."),
|
||||||
),
|
),
|
||||||
|
"Other cool apps" to listOf(
|
||||||
|
RecommendedApp("RF Sentinel", "com.rfsentinel.app", "https://github.com/CIS-C0/RFSentinel", "Spots trackers and cameras nearby."),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
/** Opened from Settings: the same list the setup guide ends with. */
|
/** Opened from Settings: the same list the setup guide ends with. */
|
||||||
|
|||||||
@@ -365,8 +365,11 @@ private fun pages(context: Context, onRequestCallScreening: () -> Unit): List<Gu
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Apps worth installing, once everything above is set up -----------------------
|
// --- Near the end, every time: usage notes. On a replay, the switch shows what was
|
||||||
if (SHOW_RECOMMENDED_APPS) {
|
// chosen before.
|
||||||
|
add(usageNotesPage(context))
|
||||||
|
|
||||||
|
// --- Last of all, every time: apps worth installing, now that everything is set up.
|
||||||
add(
|
add(
|
||||||
GuidePage(
|
GuidePage(
|
||||||
title = "Good company.",
|
title = "Good company.",
|
||||||
@@ -375,11 +378,6 @@ private fun pages(context: Context, onRequestCallScreening: () -> Unit): List<Gu
|
|||||||
content = { RecommendedAppsList() },
|
content = { RecommendedAppsList() },
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
// --- The very end, every time: usage notes. On a replay, the switch shows what was
|
|
||||||
// chosen before.
|
|
||||||
add(usageNotesPage(context))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in new issue
Block a user