mirror of
https://github.com/twinksoftware/Gearslip.git
synced 2026-11-10 16:02:25 +01:00
usage notes changes, audio changes, video quality increase.
This commit is contained in:
1 parent
55320c4d98
commit
c212c2e2d1
4 files changed
+83
-35
No files matched your search
@@ -60,6 +60,8 @@ fun GearslipApp(onDisconnect: () -> Unit, onRequestCallScreening: () -> Unit) {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
// Home's usage notes row opens Settings at that section rather than at the top.
|
||||||
|
var settingsAtUsageNotes by rememberSaveable { mutableStateOf(false) }
|
||||||
BackHandler(enabled = screen != Screen.HOME) {
|
BackHandler(enabled = screen != Screen.HOME) {
|
||||||
screen = if (screen == Screen.RECOMMENDED_APPS) Screen.SETTINGS else Screen.HOME
|
screen = if (screen == Screen.RECOMMENDED_APPS) Screen.SETTINGS else Screen.HOME
|
||||||
}
|
}
|
||||||
@@ -95,18 +97,20 @@ fun GearslipApp(onDisconnect: () -> Unit, onRequestCallScreening: () -> Unit) {
|
|||||||
when (screen) {
|
when (screen) {
|
||||||
Screen.HOME -> HomeScreen(
|
Screen.HOME -> HomeScreen(
|
||||||
onOpenLogs = { screen = Screen.LOGS },
|
onOpenLogs = { screen = Screen.LOGS },
|
||||||
onOpenSettings = { screen = Screen.SETTINGS },
|
onOpenSettings = { settingsAtUsageNotes = false; screen = Screen.SETTINGS },
|
||||||
onOpenCarPreview = { screen = Screen.CAR_PREVIEW },
|
onOpenCarPreview = { screen = Screen.CAR_PREVIEW },
|
||||||
onOpenHelp = { screen = Screen.HELP },
|
onOpenHelp = { screen = Screen.HELP },
|
||||||
|
onOpenUsageNotes = { settingsAtUsageNotes = true; screen = Screen.SETTINGS },
|
||||||
onDisconnect = onDisconnect,
|
onDisconnect = onDisconnect,
|
||||||
)
|
)
|
||||||
Screen.WHATS_NEW -> WhatsNewScreen(onBack = { screen = Screen.SETTINGS })
|
Screen.WHATS_NEW -> WhatsNewScreen(onBack = { screen = Screen.SETTINGS })
|
||||||
Screen.LOGS -> LogsScreen(onBack = { screen = Screen.HOME })
|
Screen.LOGS -> LogsScreen(onBack = { screen = Screen.HOME })
|
||||||
Screen.SETTINGS -> SettingsScreen(
|
Screen.SETTINGS -> SettingsScreen(
|
||||||
onBack = { screen = Screen.HOME },
|
onBack = { screen = Screen.HOME },
|
||||||
onOpenRecommendedApps = { screen = Screen.RECOMMENDED_APPS },
|
onOpenRecommendedApps = { settingsAtUsageNotes = false; screen = Screen.RECOMMENDED_APPS },
|
||||||
onOpenWhatsNew = { screen = Screen.WHATS_NEW },
|
onOpenWhatsNew = { settingsAtUsageNotes = false; screen = Screen.WHATS_NEW },
|
||||||
onReplayTutorial = { screen = Screen.SETUP_GUIDE },
|
onReplayTutorial = { screen = Screen.SETUP_GUIDE },
|
||||||
|
atUsageNotes = settingsAtUsageNotes,
|
||||||
)
|
)
|
||||||
Screen.RECOMMENDED_APPS -> RecommendedAppsScreen(onBack = { screen = Screen.SETTINGS })
|
Screen.RECOMMENDED_APPS -> RecommendedAppsScreen(onBack = { screen = Screen.SETTINGS })
|
||||||
Screen.CAR_PREVIEW -> CarPreviewScreen(onBack = { screen = Screen.HOME })
|
Screen.CAR_PREVIEW -> CarPreviewScreen(onBack = { screen = Screen.HOME })
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ fun HomeScreen(
|
|||||||
onOpenSettings: () -> Unit,
|
onOpenSettings: () -> Unit,
|
||||||
onOpenCarPreview: () -> Unit,
|
onOpenCarPreview: () -> Unit,
|
||||||
onOpenHelp: () -> Unit,
|
onOpenHelp: () -> Unit,
|
||||||
|
onOpenUsageNotes: () -> Unit,
|
||||||
onDisconnect: () -> Unit,
|
onDisconnect: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val status by SessionStatus.state.collectAsStateWithLifecycle()
|
val status by SessionStatus.state.collectAsStateWithLifecycle()
|
||||||
@@ -92,7 +93,6 @@ fun HomeScreen(
|
|||||||
StatusCard(status)
|
StatusCard(status)
|
||||||
Spacer(Modifier.height(16.dp))
|
Spacer(Modifier.height(16.dp))
|
||||||
UpdateCard()
|
UpdateCard()
|
||||||
UsageNotesHomeCard()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sits at the bottom, where a thumb reaches it.
|
// Sits at the bottom, where a thumb reaches it.
|
||||||
@@ -106,6 +106,9 @@ fun HomeScreen(
|
|||||||
Spacer(Modifier.height(16.dp))
|
Spacer(Modifier.height(16.dp))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UsageNotesHomeCard(onOpen = onOpenUsageNotes)
|
||||||
|
Spacer(Modifier.height(16.dp))
|
||||||
|
|
||||||
Surface(color = MaterialTheme.colorScheme.surfaceContainerHigh, shape = MaterialTheme.shapes.extraLarge) {
|
Surface(color = MaterialTheme.colorScheme.surfaceContainerHigh, shape = MaterialTheme.shapes.extraLarge) {
|
||||||
Column {
|
Column {
|
||||||
if (!live) {
|
if (!live) {
|
||||||
|
|||||||
@@ -57,6 +57,9 @@ import app.seb3thehacker.gearslip.BuildConfig
|
|||||||
import app.seb3thehacker.gearslip.CertProvider
|
import app.seb3thehacker.gearslip.CertProvider
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.snapshotFlow
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@@ -66,7 +69,16 @@ fun SettingsScreen(
|
|||||||
onOpenRecommendedApps: () -> Unit,
|
onOpenRecommendedApps: () -> Unit,
|
||||||
onOpenWhatsNew: () -> Unit,
|
onOpenWhatsNew: () -> Unit,
|
||||||
onReplayTutorial: () -> Unit,
|
onReplayTutorial: () -> Unit,
|
||||||
|
atUsageNotes: Boolean = false,
|
||||||
) {
|
) {
|
||||||
|
val scroll = rememberScrollState()
|
||||||
|
// Opened from Home's usage notes row: usage notes is the last section, so start at the end.
|
||||||
|
if (atUsageNotes) {
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
snapshotFlow { scroll.maxValue }.first { it > 0 && it < Int.MAX_VALUE }
|
||||||
|
scroll.scrollTo(scroll.maxValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
Scaffold(
|
Scaffold(
|
||||||
containerColor = MaterialTheme.colorScheme.background,
|
containerColor = MaterialTheme.colorScheme.background,
|
||||||
topBar = {
|
topBar = {
|
||||||
@@ -85,7 +97,7 @@ fun SettingsScreen(
|
|||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(padding)
|
.padding(padding)
|
||||||
.padding(horizontal = 24.dp)
|
.padding(horizontal = 24.dp)
|
||||||
.verticalScroll(rememberScrollState()),
|
.verticalScroll(scroll),
|
||||||
verticalArrangement = Arrangement.spacedBy(24.dp),
|
verticalArrangement = Arrangement.spacedBy(24.dp),
|
||||||
) {
|
) {
|
||||||
Spacer(Modifier.height(0.dp))
|
Spacer(Modifier.height(0.dp))
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package app.seb3thehacker.gearslip.ui
|
package app.seb3thehacker.gearslip.ui
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import android.content.Context
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
@@ -9,6 +10,8 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.selection.toggleable
|
import androidx.compose.foundation.selection.toggleable
|
||||||
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.filled.Check
|
||||||
import androidx.compose.material.icons.filled.Info
|
import androidx.compose.material.icons.filled.Info
|
||||||
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
||||||
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
||||||
@@ -83,11 +86,7 @@ internal fun ColumnScope.UsageStatsRows() {
|
|||||||
SettingsRow(
|
SettingsRow(
|
||||||
"Send usage notes",
|
"Send usage notes",
|
||||||
Modifier.toggleable(on, role = Role.Switch) { on = it; save() },
|
Modifier.toggleable(on, role = Role.Switch) { on = it; save() },
|
||||||
subtitle = when {
|
subtitle = buildNote(),
|
||||||
BuildConfig.DEBUG -> "Dev build: nothing is ever sent"
|
|
||||||
BuildConfig.STATS_URL.isBlank() -> "This build has no stats server, so it sends nothing"
|
|
||||||
else -> null
|
|
||||||
},
|
|
||||||
icon = Icons.Filled.Send,
|
icon = Icons.Filled.Send,
|
||||||
trailing = { Switch(checked = on, onCheckedChange = null) },
|
trailing = { Switch(checked = on, onCheckedChange = null) },
|
||||||
)
|
)
|
||||||
@@ -126,14 +125,7 @@ internal fun ColumnScope.UsageStatsRows() {
|
|||||||
)
|
)
|
||||||
AnimatedVisibility(noteOpen) {
|
AnimatedVisibility(noteOpen) {
|
||||||
// Rebuilt from the saved choices each time one changes, so it always matches.
|
// Rebuilt from the saved choices each time one changes, so it always matches.
|
||||||
val note = remember(choices) {
|
val note = remember(choices) { notePreview(context) }
|
||||||
val preview = UsageStats.preview(context, everything = true)
|
|
||||||
// Until the first drive the list is empty, so show what one entry looks like.
|
|
||||||
val example = if (preview.optJSONArray("sessions")?.length() == 0) {
|
|
||||||
"\n\nExample, not sent: one drive adds this to \"sessions\"\n" + UsageStats.exampleSession.toString(2)
|
|
||||||
} else ""
|
|
||||||
preview.toString(2) + example
|
|
||||||
}
|
|
||||||
Surface(
|
Surface(
|
||||||
color = MaterialTheme.colorScheme.surfaceContainerLowest,
|
color = MaterialTheme.colorScheme.surfaceContainerLowest,
|
||||||
shape = MaterialTheme.shapes.medium,
|
shape = MaterialTheme.shapes.medium,
|
||||||
@@ -164,23 +156,60 @@ private fun Choice(label: String, checked: Boolean, onChange: (Boolean) -> Unit)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Usage notes on Home, always: the switch, and once it's on, what to share. */
|
/** The note as it would go out now, with an example drive until a real one is in it. */
|
||||||
|
private fun notePreview(context: Context): String {
|
||||||
|
val preview = UsageStats.preview(context, everything = true)
|
||||||
|
// Until the first drive the list is empty, so show what one entry looks like.
|
||||||
|
val example = if (preview.optJSONArray("sessions")?.length() == 0) {
|
||||||
|
"\n\nExample, not sent: one drive adds this to \"sessions\"\n" + UsageStats.exampleSession.toString(2)
|
||||||
|
} else ""
|
||||||
|
return preview.toString(2) + example
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Why this build sends nothing, when it doesn't; null for a normal release. */
|
||||||
|
private fun buildNote(): String? = when {
|
||||||
|
BuildConfig.DEBUG -> "Dev build: nothing is ever sent"
|
||||||
|
BuildConfig.STATS_URL.isBlank() -> "This build has no stats server, so it sends nothing"
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Usage notes on Home, one row with one action either way. While they're off, the row is the
|
||||||
|
* switch, so opting in is a single tap. Once they're on, it shrinks to a quiet line that opens
|
||||||
|
* Settings at its usage notes section, so a driver already sharing isn't pitched again.
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
internal fun UsageNotesHomeCard(modifier: Modifier = Modifier) {
|
internal fun UsageNotesHomeCard(onOpen: () -> Unit, modifier: Modifier = Modifier) {
|
||||||
Column(modifier) {
|
val context = LocalContext.current
|
||||||
Text(
|
var on by remember { mutableStateOf(UsageStats.enabled(context)) }
|
||||||
USAGE_STATS_TITLE,
|
|
||||||
style = MaterialTheme.typography.labelLarge,
|
SettingsCard(modifier) {
|
||||||
color = MaterialTheme.colorScheme.primary,
|
if (on) {
|
||||||
modifier = Modifier.padding(start = 16.dp, bottom = 4.dp),
|
SettingsRow(
|
||||||
)
|
"Sharing usage notes",
|
||||||
Text(
|
Modifier.clickable(onClick = onOpen),
|
||||||
"Share a short note after each drive, so the next driver knows whether Gearslip works " +
|
subtitle = buildNote(),
|
||||||
"in their car. No name, account, or location.",
|
icon = Icons.Filled.Check,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
trailing = {
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
Icon(
|
||||||
modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 12.dp),
|
Icons.AutoMirrored.Filled.KeyboardArrowRight,
|
||||||
)
|
contentDescription = null,
|
||||||
SettingsCard { UsageStatsRows() }
|
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
SettingsRow(
|
||||||
|
USAGE_STATS_TITLE,
|
||||||
|
Modifier.toggleable(false, role = Role.Switch) {
|
||||||
|
UsageStats.set(context, true, UsageStats.choices(context))
|
||||||
|
on = true
|
||||||
|
},
|
||||||
|
subtitle = buildNote() ?: ("A short note after each drive tells the next driver " +
|
||||||
|
"whether their car works. No name, account, or location"),
|
||||||
|
icon = Icons.Filled.Send,
|
||||||
|
trailing = { Switch(checked = false, onCheckedChange = null) },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user