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]
|
||||
agp = "9.1.0"
|
||||
android-compileSdk = "37"
|
||||
android-compileSdk = "36"
|
||||
android-minSdk = "24"
|
||||
android-targetSdk = "37"
|
||||
android-targetSdk = "36"
|
||||
androidx-activity= "1.13.0"
|
||||
androidx-lifecycle = "2.10.0"
|
||||
compose-multiplatform = "1.12.0-alpha02"
|
||||
compose-multiplatform = "1.11.0-alpha04"
|
||||
composeunstyled = "1.49.6"
|
||||
coroutines = "1.10.2"
|
||||
geo = "0.8.0"
|
||||
|
|
@ -19,7 +19,7 @@ ktor = "3.4.1"
|
|||
logback = "1.5.32"
|
||||
maplibre = "0.12.1"
|
||||
material = "1.7.3"
|
||||
material3 = "1.11.0-alpha07"
|
||||
material3 = "1.11.0-alpha04"
|
||||
okio = "3.17.0"
|
||||
playServicesLocation = "21.3.0"
|
||||
secretsGradlePlugin = "2.0.1"
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
package moe.lava.banksia.ui.layout.info
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.foundation.background
|
||||
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.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
|
|
@ -31,8 +34,10 @@ import androidx.compose.material3.SegmentedListItem
|
|||
import androidx.compose.material3.ShapeDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.getValue
|
||||
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.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
|
|
@ -80,36 +85,120 @@ data class StopInfoPanelState(
|
|||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
private fun listColors() = ListItemDefaults.colors(
|
||||
internal fun StopInfoPanel(
|
||||
state: StopInfoPanelState,
|
||||
onEvent: (StopInfoPanelEvent) -> Unit,
|
||||
) {
|
||||
val colors = ListItemDefaults.colors(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceContainer,
|
||||
selectedContainerColor = MaterialTheme.colorScheme.primary,
|
||||
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
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
private fun MonoPlatform(
|
||||
state: StopInfoPanelState.DeparturePlatforms
|
||||
) {
|
||||
val departures = state.departures
|
||||
val lazyState = LazyListState(firstVisibleItemIndex =
|
||||
departures.indexOfFirst {
|
||||
AnimatedContent(
|
||||
targetState = state,
|
||||
contentKey = { it.id },
|
||||
// transitionSpec = { spec },
|
||||
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 ->
|
||||
val lazyState = if (departurePlatforms.size == 1) {
|
||||
LazyListState(firstVisibleItemIndex =
|
||||
departurePlatforms[0].departures.indexOfFirst {
|
||||
it.time > Clock.System.now()
|
||||
}.coerceAtLeast(0)
|
||||
)
|
||||
} else LazyListState()
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(ListItemDefaults.SegmentedGap),
|
||||
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(
|
||||
onClick = {},
|
||||
colors = listColors(),
|
||||
colors = colors,
|
||||
shapes = ListItemDefaults.segmentedShapes(
|
||||
idx,
|
||||
departures.size,
|
||||
idx + if (departurePlatforms.size > 1) 1 else 0,
|
||||
departures.size + 1
|
||||
),
|
||||
supportingContent = {
|
||||
dep.description?.let { Text(dep.description) }
|
||||
|
|
@ -155,69 +244,17 @@ private fun MonoPlatform(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 ->
|
||||
} else if (departurePlatforms.size == 1) {
|
||||
itemsIndexed(departurePlatforms[0].departures) { idx, dep ->
|
||||
// departurePlatforms[0].departures.forEachIndexed { idx, dep ->
|
||||
SegmentedListItem(
|
||||
onClick = {},
|
||||
colors = listColors(),
|
||||
colors = colors,
|
||||
shapes = ListItemDefaults.segmentedShapes(
|
||||
idx + 1,
|
||||
(departures.size + 1).coerceAtMost(6),
|
||||
idx,
|
||||
departurePlatforms[0].departures.size,
|
||||
),
|
||||
supportingContent = {
|
||||
dep.description?.let { Text(dep.description) }
|
||||
|
|
@ -245,10 +282,7 @@ private fun ManyPlatforms(
|
|||
Box(
|
||||
Modifier
|
||||
.clip(ShapeDefaults.ExtraSmall)
|
||||
.background(
|
||||
dep.routeColour
|
||||
?: MaterialTheme.colorScheme.surface
|
||||
)
|
||||
.background(dep.routeColour ?: MaterialTheme.colorScheme.surface)
|
||||
.padding(vertical = 2.dp, horizontal = 4.dp)
|
||||
) {
|
||||
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