mirror of
https://github.com/twinksoftware/Gearslip.git
synced 2026-11-11 00:12:24 +01:00
help: say most cars work, group the steps, and show the adb steps only for USB audio
This commit is contained in:
1 parent
3869258e0e
commit
6faad9fdd5
1 file changed
+85
-56
@@ -1,6 +1,7 @@
|
|||||||
package app.seb3thehacker.gearslip.ui
|
package app.seb3thehacker.gearslip.ui
|
||||||
|
|
||||||
import app.seb3thehacker.gearslip.BuildConfig
|
import app.seb3thehacker.gearslip.BuildConfig
|
||||||
|
import app.seb3thehacker.gearslip.car.CarSettings
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -23,13 +24,15 @@ import androidx.compose.material3.Scaffold
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* One step of the setup list: a sentence that says what to do, and optionally how.
|
* One step: a sentence that says what to do, and optionally how.
|
||||||
*
|
*
|
||||||
* [command] is for a step that cannot be done on the phone; it is shown as something to copy
|
* [command] is for a step that cannot be done on the phone; it is shown as something to copy
|
||||||
* rather than to read, because a mistyped package name fails silently. [onClick] is for a step
|
* rather than to read, because a mistyped package name fails silently. [onClick] is for a step
|
||||||
@@ -43,47 +46,57 @@ private class Step(
|
|||||||
val onClick: (() -> Unit)? = null,
|
val onClick: (() -> Unit)? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun setupSteps(onRequestCallScreening: () -> Unit) = listOf(
|
private val FIRST_DRIVE = listOf(
|
||||||
Step("Use a car from 2020 or earlier.", "Newer head units reject the certificate."),
|
Step("Turn off Android Auto.", "Disable or uninstall the Android Auto app, so the car talks to Gearslip instead."),
|
||||||
Step("Disable or uninstall the Android Auto app."),
|
Step("Load a certificate.", "In Settings, download one or import your own."),
|
||||||
Step("Load a certificate.", "Open Settings, then download or import a certificate."),
|
|
||||||
Step(
|
Step(
|
||||||
"Allow the microphone, so music can reach the car.",
|
"Allow the microphone.",
|
||||||
"Android asks for the microphone before it will let any app pass music along. " +
|
"Android won't let an app pass music to the car without it. Gearslip listens only when " +
|
||||||
"Gearslip listens only when you tap Reply or press the voice button. The rest of the " +
|
"you tap the mic or press the talk button on your wheel.",
|
||||||
"time it copies what a music app is playing, and nothing else.",
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun inTheCar(onRequestCallScreening: () -> Unit) = listOf(
|
||||||
|
Step(
|
||||||
|
"Plug in, or connect wirelessly.",
|
||||||
|
"Gearslip opens by itself when the car connects. For wireless, pair the phone with the " +
|
||||||
|
"car first, then tap Connect wirelessly on Home.",
|
||||||
),
|
),
|
||||||
Step(
|
Step(
|
||||||
"Optional: stop Android asking about audio every drive.",
|
"Pair over Bluetooth for calls.",
|
||||||
"Connect the phone to a computer with USB debugging turned on, and run this command. " +
|
"Calls play through the car once your phone and the car are paired. Gearslip offers to " +
|
||||||
"It tells Android to trust Gearslip with audio from now on. Skip it and everything " +
|
"pair the first time you plug in.",
|
||||||
"still works, but you have to tap Start once each time you set off.",
|
|
||||||
command = "adb shell appops set ${BuildConfig.APPLICATION_ID} PROJECT_MEDIA allow",
|
|
||||||
),
|
),
|
||||||
Step(
|
Step(
|
||||||
"Optional: keep notifications readable while music plays.",
|
"Answer calls from the car screen.",
|
||||||
"Android treats sending audio to the car like sharing your screen, and hides what your " +
|
"Set Gearslip as your caller ID and spam app. Without it, calls still ring, but only " +
|
||||||
"notifications say. Run this command too, and Gearslip switches that off only while " +
|
"the phone can answer them.",
|
||||||
"music is going to the car, then puts it back.",
|
|
||||||
command = "adb shell pm grant ${BuildConfig.APPLICATION_ID} android.permission.WRITE_SECURE_SETTINGS",
|
|
||||||
),
|
|
||||||
Step(
|
|
||||||
"Optional: let the car screen answer and decline calls.",
|
|
||||||
"This sets Gearslip as your phone's caller ID and spam-blocking app, the same kind of " +
|
|
||||||
"consent a call-blocking app asks for. Without it, calls still ring, but only the " +
|
|
||||||
"phone itself can answer or decline them.",
|
|
||||||
buttonLabel = "Set up",
|
buttonLabel = "Set up",
|
||||||
onClick = onRequestCallScreening,
|
onClick = onRequestCallScreening,
|
||||||
),
|
),
|
||||||
Step("Plug the phone into the car.", "Gearslip opens by itself when the head unit connects."),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/** The full setup reference, behind the help button: what the one-time guide walks through, kept
|
private val USB_AUDIO = listOf(
|
||||||
* here too so it can be reread without repeating the guide itself. */
|
Step(
|
||||||
|
"Stop the audio prompt each drive.",
|
||||||
|
"Connect the phone to a computer with USB debugging on, and run this once. Skip it and " +
|
||||||
|
"you tap Start each time you set off.",
|
||||||
|
command = "adb shell appops set ${BuildConfig.APPLICATION_ID} PROJECT_MEDIA allow",
|
||||||
|
),
|
||||||
|
Step(
|
||||||
|
"Keep notifications readable.",
|
||||||
|
"Android hides what notifications say while it shares audio. Run this too, and Gearslip " +
|
||||||
|
"lifts that only while music goes to the car.",
|
||||||
|
command = "adb shell pm grant ${BuildConfig.APPLICATION_ID} android.permission.WRITE_SECURE_SETTINGS",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Which cars work, and the setup the one-time guide walks through, to reread at any time. */
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun HelpScreen(onBack: () -> Unit, onRequestCallScreening: () -> Unit) {
|
fun HelpScreen(onBack: () -> Unit, onRequestCallScreening: () -> Unit) {
|
||||||
val steps = remember(onRequestCallScreening) { setupSteps(onRequestCallScreening) }
|
val car = remember(onRequestCallScreening) { inTheCar(onRequestCallScreening) }
|
||||||
|
val usb by CarSettings.pipeAudio.collectAsState()
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
containerColor = MaterialTheme.colorScheme.background,
|
containerColor = MaterialTheme.colorScheme.background,
|
||||||
@@ -104,39 +117,55 @@ fun HelpScreen(onBack: () -> Unit, onRequestCallScreening: () -> Unit) {
|
|||||||
.padding(padding)
|
.padding(padding)
|
||||||
.padding(horizontal = 24.dp)
|
.padding(horizontal = 24.dp)
|
||||||
.verticalScroll(rememberScrollState()),
|
.verticalScroll(rememberScrollState()),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(24.dp),
|
||||||
) {
|
) {
|
||||||
Spacer(Modifier.height(8.dp))
|
Spacer(Modifier.height(0.dp))
|
||||||
|
SettingsSection("Which cars work") {
|
||||||
|
Text(
|
||||||
|
"Most cars work. Starting around 2020, some cars added newer security " +
|
||||||
|
"standards that reject Gearslip's certificate, and Gearslip can't run in those cars.",
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
modifier = Modifier.padding(16.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Steps("Before your first drive", FIRST_DRIVE)
|
||||||
|
Steps("In the car", car)
|
||||||
|
if (usb) Steps("USB audio", USB_AUDIO)
|
||||||
Spacer(Modifier.height(16.dp))
|
Spacer(Modifier.height(16.dp))
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(18.dp)) {
|
}
|
||||||
steps.forEachIndexed { index, step ->
|
}
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
|
}
|
||||||
Text(
|
|
||||||
"${index + 1}",
|
/** A titled card of numbered steps. */
|
||||||
style = MaterialTheme.typography.titleMedium,
|
@Composable
|
||||||
fontWeight = FontWeight.Bold,
|
private fun Steps(title: String, steps: List<Step>) {
|
||||||
color = MaterialTheme.colorScheme.primary,
|
SettingsSection(title) {
|
||||||
modifier = Modifier.width(16.dp),
|
steps.forEachIndexed { index, step ->
|
||||||
)
|
if (index > 0) SettingsDivider()
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
Row(
|
||||||
Text(step.action, style = MaterialTheme.typography.bodyLarge, fontWeight = FontWeight.SemiBold)
|
Modifier.fillMaxWidth().padding(16.dp),
|
||||||
step.detail?.let {
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
Text(
|
) {
|
||||||
it,
|
Text(
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
"${index + 1}",
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
)
|
fontWeight = FontWeight.Bold,
|
||||||
}
|
color = MaterialTheme.colorScheme.primary,
|
||||||
step.command?.let { CopyableCommand(it) }
|
modifier = Modifier.width(16.dp),
|
||||||
if (step.onClick != null) {
|
)
|
||||||
FilledTonalButton(onClick = step.onClick, modifier = Modifier.padding(top = 4.dp)) {
|
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
Text(step.buttonLabel ?: "Open")
|
Text(step.action, style = MaterialTheme.typography.bodyLarge, fontWeight = FontWeight.SemiBold)
|
||||||
}
|
step.detail?.let {
|
||||||
}
|
Text(it, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||||
|
}
|
||||||
|
step.command?.let { CopyableCommand(it) }
|
||||||
|
if (step.onClick != null) {
|
||||||
|
FilledTonalButton(onClick = step.onClick, modifier = Modifier.padding(top = 4.dp)) {
|
||||||
|
Text(step.buttonLabel ?: "Open")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer(Modifier.height(32.dp))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user