Compare commits
1 commit
master
...
feat/depar
| Author | SHA1 | Date | |
|---|---|---|---|
| 41f3523a5a |
2 changed files with 183 additions and 207 deletions
|
|
@ -1,11 +1,11 @@
|
||||||
[versions]
|
[versions]
|
||||||
agp = "9.1.0"
|
agp = "9.1.0"
|
||||||
android-compileSdk = "37"
|
android-compileSdk = "36"
|
||||||
android-minSdk = "24"
|
android-minSdk = "24"
|
||||||
android-targetSdk = "37"
|
android-targetSdk = "36"
|
||||||
androidx-activity= "1.13.0"
|
androidx-activity= "1.13.0"
|
||||||
androidx-lifecycle = "2.10.0"
|
androidx-lifecycle = "2.10.0"
|
||||||
compose-multiplatform = "1.12.0-alpha02"
|
compose-multiplatform = "1.11.0-alpha04"
|
||||||
composeunstyled = "1.49.6"
|
composeunstyled = "1.49.6"
|
||||||
coroutines = "1.10.2"
|
coroutines = "1.10.2"
|
||||||
geo = "0.8.0"
|
geo = "0.8.0"
|
||||||
|
|
@ -19,7 +19,7 @@ ktor = "3.4.1"
|
||||||
logback = "1.5.32"
|
logback = "1.5.32"
|
||||||
maplibre = "0.12.1"
|
maplibre = "0.12.1"
|
||||||
material = "1.7.3"
|
material = "1.7.3"
|
||||||
material3 = "1.11.0-alpha07"
|
material3 = "1.11.0-alpha04"
|
||||||
okio = "3.17.0"
|
okio = "3.17.0"
|
||||||
playServicesLocation = "21.3.0"
|
playServicesLocation = "21.3.0"
|
||||||
secretsGradlePlugin = "2.0.1"
|
secretsGradlePlugin = "2.0.1"
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
package moe.lava.banksia.ui.layout.info
|
package moe.lava.banksia.ui.layout.info
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedContent
|
import androidx.compose.animation.AnimatedContent
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.animation.expandVertically
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.shrinkVertically
|
||||||
import androidx.compose.animation.togetherWith
|
import androidx.compose.animation.togetherWith
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
|
@ -12,12 +15,12 @@ import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
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.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
|
@ -31,8 +34,10 @@ import androidx.compose.material3.SegmentedListItem
|
||||||
import androidx.compose.material3.ShapeDefaults
|
import androidx.compose.material3.ShapeDefaults
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
|
@ -80,36 +85,120 @@ data class StopInfoPanelState(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun listColors() = ListItemDefaults.colors(
|
internal fun StopInfoPanel(
|
||||||
|
state: StopInfoPanelState,
|
||||||
|
onEvent: (StopInfoPanelEvent) -> Unit,
|
||||||
|
) {
|
||||||
|
val colors = ListItemDefaults.colors(
|
||||||
containerColor = MaterialTheme.colorScheme.surfaceContainer,
|
containerColor = MaterialTheme.colorScheme.surfaceContainer,
|
||||||
selectedContainerColor = MaterialTheme.colorScheme.primary,
|
selectedContainerColor = MaterialTheme.colorScheme.primary,
|
||||||
selectedContentColor = MaterialTheme.colorScheme.onPrimary,
|
selectedContentColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
)
|
)
|
||||||
|
// val spec = fadeIn(MaterialTheme.motionScheme.defaultEffectsSpec())
|
||||||
|
// .togetherWith(fadeOut(MaterialTheme.motionScheme.defaultEffectsSpec()))
|
||||||
|
val spec = fadeIn(tween(300, 300)) togetherWith fadeOut(tween(300))
|
||||||
|
|
||||||
@Composable
|
AnimatedContent(
|
||||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
targetState = state,
|
||||||
private fun MonoPlatform(
|
contentKey = { it.id },
|
||||||
state: StopInfoPanelState.DeparturePlatforms
|
// transitionSpec = { spec },
|
||||||
) {
|
transitionSpec = { spec },
|
||||||
val departures = state.departures
|
) { state ->
|
||||||
val lazyState = LazyListState(firstVisibleItemIndex =
|
Column(Modifier.fillMaxWidth().fillMaxHeight()) {
|
||||||
departures.indexOfFirst {
|
Row {
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
state.name,
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
textAlign = TextAlign.Start
|
||||||
|
)
|
||||||
|
state.subname?.let {
|
||||||
|
Text(
|
||||||
|
"/ $it",
|
||||||
|
modifier = Modifier.padding(start = 5.dp),
|
||||||
|
style = MaterialTheme.typography.titleSmall,
|
||||||
|
color = Color.Gray,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
textAlign = TextAlign.Start
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IconButton(
|
||||||
|
onClick = { onEvent(StopInfoPanelEvent.ToggleGrouping) },
|
||||||
|
) { Icon(Icons.Default.Edit, null) }
|
||||||
|
}
|
||||||
|
Spacer(Modifier.height(10.dp))
|
||||||
|
AnimatedContent(
|
||||||
|
targetState = state.departures,
|
||||||
|
transitionSpec = { spec },
|
||||||
|
) { departures ->
|
||||||
|
departures?.let { departurePlatforms ->
|
||||||
|
val lazyState = if (departurePlatforms.size == 1) {
|
||||||
|
LazyListState(firstVisibleItemIndex =
|
||||||
|
departurePlatforms[0].departures.indexOfFirst {
|
||||||
it.time > Clock.System.now()
|
it.time > Clock.System.now()
|
||||||
}.coerceAtLeast(0)
|
}.coerceAtLeast(0)
|
||||||
)
|
)
|
||||||
|
} else LazyListState()
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
verticalArrangement = Arrangement.spacedBy(ListItemDefaults.SegmentedGap),
|
verticalArrangement = Arrangement.spacedBy(ListItemDefaults.SegmentedGap),
|
||||||
state = lazyState,
|
state = lazyState,
|
||||||
) {
|
) {
|
||||||
itemsIndexed(departures) { idx, dep ->
|
if (departurePlatforms.size > 1) {
|
||||||
|
items(departurePlatforms) { (platform, departures) ->
|
||||||
|
// departurePlatforms.forEach { (platform, departures) ->
|
||||||
|
var expanded by rememberSaveable { mutableStateOf(true) }
|
||||||
|
val base = ListItemDefaults.segmentedShapes(0, 2)
|
||||||
|
val large = MaterialTheme.shapes.large
|
||||||
|
|
||||||
|
if (departurePlatforms.size > 1) {
|
||||||
|
SegmentedListItem(
|
||||||
|
onClick = { expanded = !expanded },
|
||||||
|
colors = colors,
|
||||||
|
shapes = if (expanded) base else base.copy(shape = large),
|
||||||
|
trailingContent = {
|
||||||
|
Icon(
|
||||||
|
painterResource(if (expanded) Res.drawable.arrow_drop_up else Res.drawable.arrow_drop_down),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.background(
|
||||||
|
if (expanded) MaterialTheme.colorScheme.surface else Color.Transparent,
|
||||||
|
shape = RoundedCornerShape(100)
|
||||||
|
)
|
||||||
|
.padding(6.dp),
|
||||||
|
tint = MaterialTheme.colorScheme.onSurface,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = platform,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = expanded,
|
||||||
|
enter = expandVertically(MaterialTheme.motionScheme.fastSpatialSpec()),
|
||||||
|
exit = shrinkVertically(MaterialTheme.motionScheme.fastSpatialSpec()),
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.height(200.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(ListItemDefaults.SegmentedGap)
|
||||||
|
) {
|
||||||
|
departures
|
||||||
|
.filter { it.time > Clock.System.now() }
|
||||||
|
.take(5)
|
||||||
|
.forEachIndexed { idx, dep ->
|
||||||
SegmentedListItem(
|
SegmentedListItem(
|
||||||
onClick = {},
|
onClick = {},
|
||||||
colors = listColors(),
|
colors = colors,
|
||||||
shapes = ListItemDefaults.segmentedShapes(
|
shapes = ListItemDefaults.segmentedShapes(
|
||||||
idx,
|
idx + if (departurePlatforms.size > 1) 1 else 0,
|
||||||
departures.size,
|
departures.size + 1
|
||||||
),
|
),
|
||||||
supportingContent = {
|
supportingContent = {
|
||||||
dep.description?.let { Text(dep.description) }
|
dep.description?.let { Text(dep.description) }
|
||||||
|
|
@ -155,69 +244,17 @@ private fun MonoPlatform(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Spacer(modifier = Modifier.height(10.dp))
|
||||||
@Composable
|
|
||||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
|
||||||
private fun ManyPlatforms(
|
|
||||||
state: List<StopInfoPanelState.DeparturePlatforms>,
|
|
||||||
) {
|
|
||||||
val expandedList = remember { mutableStateListOf(*Array(state.size) { true }) }
|
|
||||||
LazyColumn(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
) {
|
|
||||||
state.forEachIndexed { idx, depInfo ->
|
|
||||||
val (platform, departures) = depInfo
|
|
||||||
val expanded = expandedList[idx]
|
|
||||||
stickyHeader(key = "header_${depInfo.hashCode()}") {
|
|
||||||
val base = ListItemDefaults.segmentedShapes(0, 2)
|
|
||||||
val large = MaterialTheme.shapes.large
|
|
||||||
|
|
||||||
Box(
|
|
||||||
Modifier
|
|
||||||
.animateItem()
|
|
||||||
.background(MaterialTheme.colorScheme.surfaceContainerLow)
|
|
||||||
.padding(bottom = ListItemDefaults.SegmentedGap)
|
|
||||||
) {
|
|
||||||
SegmentedListItem(
|
|
||||||
onClick = { expandedList[idx] = !expandedList[idx] },
|
|
||||||
colors = listColors(),
|
|
||||||
shapes = if (expanded) base else base.copy(shape = large),
|
|
||||||
trailingContent = {
|
|
||||||
Icon(
|
|
||||||
painterResource(if (expanded) Res.drawable.arrow_drop_up else Res.drawable.arrow_drop_down),
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier
|
|
||||||
.background(
|
|
||||||
if (expanded) MaterialTheme.colorScheme.surface else Color.Transparent,
|
|
||||||
shape = RoundedCornerShape(100)
|
|
||||||
)
|
|
||||||
.padding(6.dp),
|
|
||||||
tint = MaterialTheme.colorScheme.onSurface,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = platform,
|
|
||||||
style = MaterialTheme.typography.labelLarge,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
} else if (departurePlatforms.size == 1) {
|
||||||
}
|
itemsIndexed(departurePlatforms[0].departures) { idx, dep ->
|
||||||
|
// departurePlatforms[0].departures.forEachIndexed { idx, dep ->
|
||||||
if (expanded) {
|
|
||||||
item(key = "items_${depInfo.hashCode()}") {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.animateItem(),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(ListItemDefaults.SegmentedGap),
|
|
||||||
) {
|
|
||||||
departures.filter { it.time > Clock.System.now() }.take(5)
|
|
||||||
.forEachIndexed { idx, dep ->
|
|
||||||
SegmentedListItem(
|
SegmentedListItem(
|
||||||
onClick = {},
|
onClick = {},
|
||||||
colors = listColors(),
|
colors = colors,
|
||||||
shapes = ListItemDefaults.segmentedShapes(
|
shapes = ListItemDefaults.segmentedShapes(
|
||||||
idx + 1,
|
idx,
|
||||||
(departures.size + 1).coerceAtMost(6),
|
departurePlatforms[0].departures.size,
|
||||||
),
|
),
|
||||||
supportingContent = {
|
supportingContent = {
|
||||||
dep.description?.let { Text(dep.description) }
|
dep.description?.let { Text(dep.description) }
|
||||||
|
|
@ -245,10 +282,7 @@ private fun ManyPlatforms(
|
||||||
Box(
|
Box(
|
||||||
Modifier
|
Modifier
|
||||||
.clip(ShapeDefaults.ExtraSmall)
|
.clip(ShapeDefaults.ExtraSmall)
|
||||||
.background(
|
.background(dep.routeColour ?: MaterialTheme.colorScheme.surface)
|
||||||
dep.routeColour
|
|
||||||
?: MaterialTheme.colorScheme.surface
|
|
||||||
)
|
|
||||||
.padding(vertical = 2.dp, horizontal = 4.dp)
|
.padding(vertical = 2.dp, horizontal = 4.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
|
|
@ -267,64 +301,6 @@ private fun ManyPlatforms(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
item(key = "spacer_${depInfo.hashCode()}") {
|
|
||||||
Spacer(
|
|
||||||
modifier = Modifier.animateItem().height(10.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
|
||||||
@Composable
|
|
||||||
internal fun StopInfoPanel(
|
|
||||||
state: StopInfoPanelState,
|
|
||||||
onEvent: (StopInfoPanelEvent) -> Unit,
|
|
||||||
) {
|
|
||||||
val spec = fadeIn(tween(300, 300)) togetherWith fadeOut(tween(300))
|
|
||||||
|
|
||||||
AnimatedContent(
|
|
||||||
targetState = state,
|
|
||||||
contentKey = { it.id },
|
|
||||||
transitionSpec = { spec },
|
|
||||||
) { state ->
|
|
||||||
Column(Modifier.fillMaxWidth().fillMaxHeight()) {
|
|
||||||
Row {
|
|
||||||
Column {
|
|
||||||
Text(
|
|
||||||
state.name,
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
fontWeight = FontWeight.SemiBold,
|
|
||||||
textAlign = TextAlign.Start
|
|
||||||
)
|
|
||||||
state.subname?.let {
|
|
||||||
Text(
|
|
||||||
"/ $it",
|
|
||||||
modifier = Modifier.padding(start = 5.dp),
|
|
||||||
style = MaterialTheme.typography.titleSmall,
|
|
||||||
color = Color.Gray,
|
|
||||||
fontWeight = FontWeight.SemiBold,
|
|
||||||
textAlign = TextAlign.Start
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
IconButton(
|
|
||||||
onClick = { onEvent(StopInfoPanelEvent.ToggleGrouping) },
|
|
||||||
) { Icon(Icons.Default.Edit, null) }
|
|
||||||
}
|
|
||||||
Spacer(Modifier.height(10.dp))
|
|
||||||
AnimatedContent(
|
|
||||||
targetState = state.departures,
|
|
||||||
transitionSpec = { spec },
|
|
||||||
) { departures ->
|
|
||||||
departures?.let { departurePlatforms ->
|
|
||||||
if (departurePlatforms.size > 1) {
|
|
||||||
ManyPlatforms(departurePlatforms)
|
|
||||||
} else if (departurePlatforms.size == 1) {
|
|
||||||
MonoPlatform(departurePlatforms[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue