mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-12 11:31:08 +00:00
Move build status to launcher rail footer
This commit is contained in:
1 parent
0cdb945d9c
commit
3e864fda17
1 file changed
+87
-86
@@ -54,12 +54,12 @@ import androidx.compose.foundation.layout.fillMaxHeight
|
|||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
import androidx.compose.foundation.layout.offset
|
import androidx.compose.foundation.layout.offset
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.systemBarsPadding
|
import androidx.compose.foundation.layout.systemBarsPadding
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.layout.widthIn
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
@@ -101,6 +101,9 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.semantics.Role
|
import androidx.compose.ui.semantics.Role
|
||||||
|
import androidx.compose.ui.semantics.contentDescription
|
||||||
|
import androidx.compose.ui.semantics.semantics
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.IntOffset
|
import androidx.compose.ui.unit.IntOffset
|
||||||
@@ -291,82 +294,89 @@ private fun Rail(
|
|||||||
) {
|
) {
|
||||||
val colors = MaterialTheme.colorScheme
|
val colors = MaterialTheme.colorScheme
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier.background(colors.surface).verticalScroll(rememberScrollState()).padding(horizontal = 10.dp, vertical = 12.dp),
|
modifier = modifier.background(colors.surface).padding(horizontal = 10.dp, vertical = 12.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(2.dp),
|
|
||||||
) {
|
) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(start = 8.dp, bottom = 10.dp)) {
|
Column(
|
||||||
Image(painterResource(R.drawable.logo), null, modifier = Modifier.size(30.dp))
|
modifier = Modifier.weight(1f).fillMaxWidth().verticalScroll(rememberScrollState()),
|
||||||
Spacer(Modifier.width(10.dp))
|
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
Column {
|
) {
|
||||||
Text("DroidDeck", fontSize = 18.sp, fontWeight = FontWeight.Bold, color = colors.onBackground)
|
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(start = 8.dp, bottom = 10.dp)) {
|
||||||
val status = when {
|
Image(painterResource(R.drawable.logo), null, modifier = Modifier.size(30.dp))
|
||||||
s.busy -> if (s.percent >= 0) "${s.stage} ${s.percent}%" else s.stage
|
Spacer(Modifier.width(10.dp))
|
||||||
!s.ready -> "runtime not installed"
|
Column {
|
||||||
s.available != null && s.available != s.installed -> "runtime update available"
|
Text("DroidDeck", fontSize = 18.sp, fontWeight = FontWeight.Bold, color = colors.onBackground)
|
||||||
else -> null
|
val status = when {
|
||||||
|
s.busy -> if (s.percent >= 0) "${s.stage} ${s.percent}%" else s.stage
|
||||||
|
!s.ready -> "runtime not installed"
|
||||||
|
s.available != null && s.available != s.installed -> "runtime update available"
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
if (status != null) Text(status, fontSize = 11.sp, color = colors.onSurfaceVariant, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||||
}
|
}
|
||||||
if (status != null) Text(status, fontSize = 11.sp, color = colors.onSurfaceVariant, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
|
||||||
}
|
}
|
||||||
}
|
AnimatedVisibility(s.busy, enter = expandVertically() + fadeIn(), exit = shrinkVertically() + fadeOut()) {
|
||||||
AnimatedVisibility(s.busy, enter = expandVertically() + fadeIn(), exit = shrinkVertically() + fadeOut()) {
|
LinearProgressIndicator(modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp))
|
||||||
LinearProgressIndicator(modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp))
|
}
|
||||||
}
|
|
||||||
|
|
||||||
var lastRunning by remember { mutableStateOf("") }
|
var lastRunning by remember { mutableStateOf("") }
|
||||||
if (s.running != null) lastRunning = s.running
|
if (s.running != null) lastRunning = s.running
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
s.running != null,
|
s.running != null,
|
||||||
enter = expandVertically(Motion.sp(0.75f)) + fadeIn(Motion.tw(300)) + slideInVertically(Motion.sp(0.6f)) { -it / 2 },
|
enter = expandVertically(Motion.sp(0.75f)) + fadeIn(Motion.tw(300)) + slideInVertically(Motion.sp(0.6f)) { -it / 2 },
|
||||||
exit = shrinkVertically(Motion.tw(220)) + fadeOut(Motion.tw(180)),
|
exit = shrinkVertically(Motion.tw(220)) + fadeOut(Motion.tw(180)),
|
||||||
) { RunningTile(lastRunning, a.onResume) }
|
) { RunningTile(lastRunning, a.onResume) }
|
||||||
|
|
||||||
val pal = LocalPalette.current
|
val pal = LocalPalette.current
|
||||||
var navOrigin by remember { mutableStateOf(Offset.Zero) }
|
var navOrigin by remember { mutableStateOf(Offset.Zero) }
|
||||||
val positions = remember { mutableStateMapOf<String, Rect>() }
|
val positions = remember { mutableStateMapOf<String, Rect>() }
|
||||||
Box(modifier = Modifier.fillMaxWidth().onGloballyPositioned { val o = it.positionInRoot(); if (o != navOrigin) navOrigin = o }) {
|
Box(modifier = Modifier.fillMaxWidth().onGloballyPositioned { val o = it.positionInRoot(); if (o != navOrigin) navOrigin = o }) {
|
||||||
val target = positions[selected]?.translate(-navOrigin)
|
val target = positions[selected]?.translate(-navOrigin)
|
||||||
val y by animateFloatAsState(target?.top ?: 0f, Motion.sp(0.72f, Spring.StiffnessLow), label = "indY")
|
val y by animateFloatAsState(target?.top ?: 0f, Motion.sp(0.72f, Spring.StiffnessLow), label = "indY")
|
||||||
val x by animateFloatAsState(target?.left ?: 0f, Motion.sp(0.9f), label = "indX")
|
val x by animateFloatAsState(target?.left ?: 0f, Motion.sp(0.9f), label = "indX")
|
||||||
val h by animateFloatAsState(target?.height ?: 0f, Motion.sp(1f, Spring.StiffnessMediumLow), label = "indH")
|
val h by animateFloatAsState(target?.height ?: 0f, Motion.sp(1f, Spring.StiffnessMediumLow), label = "indH")
|
||||||
val w by animateFloatAsState(target?.width ?: 0f, Motion.sp(1f, Spring.StiffnessMediumLow), label = "indW")
|
val w by animateFloatAsState(target?.width ?: 0f, Motion.sp(1f, Spring.StiffnessMediumLow), label = "indW")
|
||||||
val alpha by animateFloatAsState(if (target != null) 1f else 0f, Motion.tw(250), label = "indA")
|
val alpha by animateFloatAsState(if (target != null) 1f else 0f, Motion.tw(250), label = "indA")
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.offset { IntOffset(x.roundToInt(), y.roundToInt()) }
|
.offset { IntOffset(x.roundToInt(), y.roundToInt()) }
|
||||||
.width(with(density) { w.toDp() }).height(with(density) { h.toDp() })
|
.width(with(density) { w.toDp() }).height(with(density) { h.toDp() })
|
||||||
.alpha(alpha)
|
.alpha(alpha)
|
||||||
.graphicsLayer { shadowElevation = 6.dp.toPx(); shape = Shape10; clip = false; ambientShadowColor = pal.signal; spotShadowColor = pal.signal }
|
.graphicsLayer { shadowElevation = 6.dp.toPx(); shape = Shape10; clip = false; ambientShadowColor = pal.signal; spotShadowColor = pal.signal }
|
||||||
.clip(Shape10)
|
.clip(Shape10)
|
||||||
.background(Brush.linearGradient(listOf(colors.primary, pal.primary2))),
|
.background(Brush.linearGradient(listOf(colors.primary, pal.primary2))),
|
||||||
|
)
|
||||||
|
val register: (String, LayoutCoordinates) -> Unit = { key, c ->
|
||||||
|
val o = c.positionInRoot(); val r = Rect(o.x, o.y, o.x + c.size.width, o.y + c.size.height)
|
||||||
|
if (positions[key] != r) positions[key] = r
|
||||||
|
}
|
||||||
|
val unregister: (String) -> Unit = { positions.remove(it) }
|
||||||
|
Column(modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||||
|
if (s.isHomeApp) {
|
||||||
|
NavItem("Android apps", "android-apps", selected == "android-apps", count = s.androidApps.size,
|
||||||
|
register = register, unregister = unregister) { onSelect("android-apps") }
|
||||||
|
}
|
||||||
|
NavItem("Steam", "steam", selected == "steam", count = s.steamGames.size, register = register, unregister = unregister) { onSelect("steam") }
|
||||||
|
NavItem("Desktop", "desktop", selected == "desktop", count = s.emulators.count { it.installed }, register = register, unregister = unregister) { onSelect("desktop") }
|
||||||
|
NavItem("Setup", "setup", selected == "setup", register = register, unregister = unregister) { onSelect("setup") }
|
||||||
|
Spacer(Modifier.height(6.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val creditsSrc = remember { MutableInteractionSource() }
|
||||||
|
val creditsHot = rememberHot(creditsSrc)
|
||||||
|
Text(
|
||||||
|
"credits", fontSize = 11.sp, color = if (creditsHot) LocalPalette.current.signal else colors.onSurfaceVariant,
|
||||||
|
modifier = Modifier.clip(Shape10)
|
||||||
|
.border(2.dp, if (creditsHot) LocalPalette.current.signal else Color.Transparent, Shape10)
|
||||||
|
.hoverable(creditsSrc).clickable(interactionSource = creditsSrc, indication = null, onClick = a.onCredits)
|
||||||
|
.padding(horizontal = 10.dp, vertical = 8.dp),
|
||||||
)
|
)
|
||||||
val register: (String, LayoutCoordinates) -> Unit = { key, c ->
|
|
||||||
val o = c.positionInRoot(); val r = Rect(o.x, o.y, o.x + c.size.width, o.y + c.size.height)
|
|
||||||
if (positions[key] != r) positions[key] = r
|
|
||||||
}
|
|
||||||
val unregister: (String) -> Unit = { positions.remove(it) }
|
|
||||||
Column(modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(2.dp)) {
|
|
||||||
if (s.isHomeApp) {
|
|
||||||
NavItem("Android apps", "android-apps", selected == "android-apps", count = s.androidApps.size,
|
|
||||||
register = register, unregister = unregister) { onSelect("android-apps") }
|
|
||||||
}
|
|
||||||
NavItem("Steam", "steam", selected == "steam", count = s.steamGames.size, register = register, unregister = unregister) { onSelect("steam") }
|
|
||||||
NavItem("Desktop", "desktop", selected == "desktop", count = s.emulators.count { it.installed }, register = register, unregister = unregister) { onSelect("desktop") }
|
|
||||||
NavItem("Setup", "setup", selected == "setup", register = register, unregister = unregister) { onSelect("setup") }
|
|
||||||
Spacer(Modifier.height(6.dp))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
BuildStatus(
|
||||||
// Outlined when a controller is on it, like every other control; the ripple alone was
|
label = s.buildLabel,
|
||||||
// a faint box on the dark rail.
|
onCheckLatest = a.onCheckLatestBuild,
|
||||||
val creditsSrc = remember { MutableInteractionSource() }
|
modifier = Modifier.align(Alignment.Start).padding(start = 8.dp, top = 4.dp, bottom = 2.dp),
|
||||||
val creditsHot = rememberHot(creditsSrc)
|
|
||||||
Text(
|
|
||||||
"credits", fontSize = 11.sp, color = if (creditsHot) LocalPalette.current.signal else colors.onSurfaceVariant,
|
|
||||||
modifier = Modifier.clip(Shape10)
|
|
||||||
.border(2.dp, if (creditsHot) LocalPalette.current.signal else Color.Transparent, Shape10)
|
|
||||||
.hoverable(creditsSrc).clickable(interactionSource = creditsSrc, indication = null, onClick = a.onCredits)
|
|
||||||
.padding(horizontal = 10.dp, vertical = 8.dp),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -471,13 +481,6 @@ private fun Pane(s: FrontEndState, selected: String, a: FrontEndActions, page: (
|
|||||||
},
|
},
|
||||||
label = "pane",
|
label = "pane",
|
||||||
) { key -> if (page != null && key == s.pageKey) page() else Content(s, key, a, Modifier.fillMaxSize(), onSelect) }
|
) { key -> if (page != null && key == s.pageKey) page() else Content(s, key, a, Modifier.fillMaxSize(), onSelect) }
|
||||||
if (page == null && s.pageKey == null && selected == "steam") {
|
|
||||||
BuildStatus(
|
|
||||||
label = s.buildLabel,
|
|
||||||
onCheckLatest = a.onCheckLatestBuild,
|
|
||||||
modifier = Modifier.align(Alignment.TopEnd).padding(12.dp),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -489,12 +492,10 @@ private fun BuildStatus(label: String, onCheckLatest: () -> Unit, modifier: Modi
|
|||||||
val hot = rememberHot(interaction)
|
val hot = rememberHot(interaction)
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(9.dp),
|
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.widthIn(max = 360.dp)
|
.clip(RoundedCornerShape(6.dp))
|
||||||
.clip(Shape12)
|
.semantics { contentDescription = "Build $label. Check for newest build." }
|
||||||
.background(colors.surface.copy(alpha = 0.96f))
|
|
||||||
.border(1.5.dp, if (hot) pal.signal else pal.line2, Shape12)
|
|
||||||
.hoverable(interaction)
|
.hoverable(interaction)
|
||||||
.clickable(
|
.clickable(
|
||||||
interactionSource = interaction,
|
interactionSource = interaction,
|
||||||
@@ -502,11 +503,11 @@ private fun BuildStatus(label: String, onCheckLatest: () -> Unit, modifier: Modi
|
|||||||
role = Role.Button,
|
role = Role.Button,
|
||||||
onClick = onCheckLatest,
|
onClick = onCheckLatest,
|
||||||
)
|
)
|
||||||
.padding(horizontal = 11.dp, vertical = 7.dp),
|
.heightIn(min = 40.dp)
|
||||||
|
.padding(horizontal = 8.dp),
|
||||||
) {
|
) {
|
||||||
Text(label, modifier = Modifier.weight(1f), fontSize = 12.sp, fontWeight = FontWeight.SemiBold, color = colors.onBackground, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
Text(label, fontSize = 10.sp, fontFamily = FontFamily.Monospace, fontWeight = FontWeight.Medium, color = if (hot) colors.onSurfaceVariant else colors.onSurfaceVariant.copy(alpha = 0.72f), maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||||
Icon(Icons.Filled.Refresh, contentDescription = null, tint = if (hot) pal.signal else colors.onSurfaceVariant, modifier = Modifier.size(15.dp))
|
Icon(Icons.Filled.Refresh, contentDescription = null, tint = if (hot) pal.signal else colors.onSurfaceVariant.copy(alpha = 0.72f), modifier = Modifier.size(12.dp))
|
||||||
Text("Check latest", fontSize = 11.sp, fontWeight = FontWeight.SemiBold, color = if (hot) pal.signal else colors.onBackground, maxLines = 1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user