Updated dependencies and gradle plugin, minor fixes

develop
Arty Bishop 2024-12-06 14:45:56 +00:00
rodzic e2415cda4b
commit a523684ffb
9 zmienionych plików z 54 dodań i 537 usunięć

Wyświetl plik

@ -46,6 +46,7 @@ dependencies {
implementation(libs.androidx.room.runtime)
ksp(libs.androidx.room.compiler)
implementation(platform(libs.compose.bom))
implementation(libs.bundles.composeAll)
implementation(libs.other.okhttp)

Wyświetl plik

@ -29,7 +29,7 @@ import com.rtbishop.look4sat.presentation.satellites.SatellitesScreen
import com.rtbishop.look4sat.presentation.satellites.SatellitesViewModel
import com.rtbishop.look4sat.presentation.settings.SettingsScreen
sealed class Screen(var title: String, var icon: Int, var route: String) {
private sealed class Screen(var title: String, var icon: Int, var route: String) {
data object Main : Screen("Main", R.drawable.ic_sputnik, "main")
data object Radar : Screen("Radar", R.drawable.ic_sputnik, "radar")
data object Satellites : Screen("Satellites", R.drawable.ic_sputnik, "satellites")
@ -61,17 +61,19 @@ private fun NavBarScreen(navToRadar: (Int, Long) -> Unit) {
val navToPasses = { innerNavController.navigate(Screen.Passes.route) }
Scaffold(bottomBar = { MainNavBar(navController = innerNavController) }) { innerPadding ->
Box(modifier = Modifier.padding(innerPadding)) {
NavHost(innerNavController, startDestination = Screen.Passes.route) {
NavHost(navController = innerNavController, startDestination = Screen.Passes.route) {
composable(Screen.Satellites.route) {
val viewModel = viewModel(
SatellitesViewModel::class.java, factory = SatellitesViewModel.Factory
modelClass = SatellitesViewModel::class.java,
factory = SatellitesViewModel.Factory
)
val uiState = viewModel.uiState.collectAsStateWithLifecycle().value
SatellitesScreen(uiState, navToPasses)
}
composable(Screen.Passes.route) {
val viewModel = viewModel(
PassesViewModel::class.java, factory = PassesViewModel.Factory
modelClass = PassesViewModel::class.java,
factory = PassesViewModel.Factory
)
val uiState = viewModel.uiState.value
PassesScreen(uiState, navToRadar)
@ -91,18 +93,18 @@ private fun MainNavBar(navController: NavController) {
val currentRoute = currentBackStackEntry.value?.destination?.route
NavigationBar {
items.forEach { item ->
NavigationBarItem(selected = currentRoute?.contains(item.route) ?: false,
NavigationBarItem(
icon = { Icon(painterResource(item.icon), item.title) },
label = { Text(item.title) },
selected = currentRoute?.contains(item.route) ?: false,
onClick = {
navController.navigate(item.route) {
navController.graph.startDestinationRoute?.let {
popUpTo(it) { saveState = false }
}
popUpTo(navController.graph.startDestinationId) { saveState = false }
launchSingleTop = true
restoreState = false
}
},
icon = { Icon(painterResource(id = item.icon), contentDescription = item.title) },
label = { Text(item.title) })
}
)
}
}
}

Wyświetl plik

@ -34,7 +34,6 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp

Wyświetl plik

@ -1,187 +0,0 @@
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.rtbishop.look4sat.presentation.components
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.center
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.PathFillType
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.drawscope.rotate
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
/**
* The default indicator for Compose pull-to-refresh, based on Android's SwipeRefreshLayout.
* @param refreshing A boolean representing whether a refresh is occurring.
* @param state The [PullRefreshState] which controls where and how the indicator will be drawn.
* @param modifier Modifiers for the indicator.
* @param backgroundColor The color of the indicator's background.
* @param contentColor The color of the indicator's arc and arrow.
* @param scale A boolean controlling whether the indicator's size scales with pull progress or not.
*/
@Composable
fun PullRefreshIndicator(
refreshing: Boolean,
state: PullRefreshState,
modifier: Modifier = Modifier,
backgroundColor: Color = MaterialTheme.colorScheme.surface,
contentColor: Color = contentColorFor(backgroundColor),
scale: Boolean = false
) {
val showElevation by remember(refreshing, state) {
derivedStateOf { refreshing || state.position > 0.5f }
}
Surface(
modifier = modifier
.size(indicatorSize)
.pullRefreshIndicatorTransform(state, scale),
shape = spinnerShape,
color = backgroundColor,
shadowElevation = if (showElevation) elevation else 0.dp,
) {
Crossfade(
targetState = refreshing,
animationSpec = tween(durationMillis = CROSSFADE_DURATION_MS),
label = "pullToRefresh"
) { refreshing ->
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
val spinnerSize = (arcRadius + strokeWidth).times(2)
if (refreshing) {
CircularProgressIndicator(
color = contentColor,
strokeWidth = strokeWidth,
modifier = Modifier.size(spinnerSize),
)
} else {
CircularArrowIndicator(state, contentColor, Modifier.size(spinnerSize))
}
}
}
}
}
/**
* Modifier.size MUST be specified.
*/
@Composable
private fun CircularArrowIndicator(state: PullRefreshState, color: Color, modifier: Modifier) {
val path = remember { Path().apply { fillType = PathFillType.EvenOdd } }
Canvas(modifier.semantics { contentDescription = "Refreshing" }) {
val values = ArrowValues(state.progress)
rotate(degrees = values.rotation) {
val arcRadius = arcRadius.toPx() + strokeWidth.toPx() / 2f
val arcBounds = Rect(
size.center.x - arcRadius,
size.center.y - arcRadius,
size.center.x + arcRadius,
size.center.y + arcRadius
)
drawArc(
color = color,
alpha = values.alpha,
startAngle = values.startAngle,
sweepAngle = values.endAngle - values.startAngle,
useCenter = false,
topLeft = arcBounds.topLeft,
size = arcBounds.size,
style = Stroke(width = strokeWidth.toPx(), cap = StrokeCap.Square)
)
drawArrow(path, arcBounds, color, values)
}
}
}
@Immutable
private class ArrowValues(
val alpha: Float, val rotation: Float, val startAngle: Float, val endAngle: Float, val scale: Float
)
private fun ArrowValues(progress: Float): ArrowValues {
// Discard first 40% of progress. Scale remaining progress to full range between 0 and 100%.
val adjustedPercent = max(min(1f, progress) - 0.4f, 0f) * 5 / 3
// How far beyond the threshold pull has gone, as a percentage of the threshold.
val overshootPercent = abs(progress) - 1.0f
// Limit the overshoot to 200%. Linear between 0 and 200.
val linearTension = overshootPercent.coerceIn(0f, 2f)
// Non-linear tension. Increases with linearTension, but at a decreasing rate.
val tensionPercent = linearTension - linearTension.pow(2) / 4
// Calculations based on SwipeRefreshLayout specification.
val alpha = progress.coerceIn(0f, 1f)
val endTrim = adjustedPercent * MAX_PROGRESS_ARC
val rotation = (-0.25f + 0.4f * adjustedPercent + tensionPercent) * 0.5f
val startAngle = rotation * 360
val endAngle = (rotation + endTrim) * 360
val scale = min(1f, adjustedPercent)
return ArrowValues(alpha, rotation, startAngle, endAngle, scale)
}
private fun DrawScope.drawArrow(arrow: Path, bounds: Rect, color: Color, values: ArrowValues) {
arrow.reset()
arrow.moveTo(0f, 0f) // Move to left corner
arrow.lineTo(x = arrowWidth.toPx() * values.scale, y = 0f) // Line to right corner
// Line to tip of arrow
arrow.lineTo(x = arrowWidth.toPx() * values.scale / 2, y = arrowHeight.toPx() * values.scale)
val radius = min(bounds.width, bounds.height) / 2f
val inset = arrowWidth.toPx() * values.scale / 2f
arrow.translate(Offset(x = radius + bounds.center.x - inset, y = bounds.center.y + strokeWidth.toPx() / 2f))
arrow.close()
rotate(degrees = values.endAngle) {
drawPath(path = arrow, color = color, alpha = values.alpha)
}
}
private const val CROSSFADE_DURATION_MS = 100
private const val MAX_PROGRESS_ARC = 0.8f
private val indicatorSize = 40.dp
private val spinnerShape = CircleShape
private val arcRadius = 7.5.dp
private val strokeWidth = 2.5.dp
private val arrowWidth = 10.dp
private val arrowHeight = 5.dp
private val elevation = 6.dp

Wyświetl plik

@ -1,128 +0,0 @@
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.rtbishop.look4sat.presentation.components
import androidx.compose.animation.core.LinearOutSlowInEasing
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.NestedScrollSource.Companion.Drag
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.debugInspectorInfo
import androidx.compose.ui.platform.inspectable
import androidx.compose.ui.unit.Velocity
/**
* A modifier for translating the position and scaling the size of a pull-to-refresh indicator
* based on the given [PullRefreshState].
* @param state The [PullRefreshState] which determines the position of the indicator.
* @param scale A boolean controlling whether the indicator's size scales with pull progress or not.
*/
fun Modifier.pullRefreshIndicatorTransform(
state: PullRefreshState,
scale: Boolean = false,
) = composed(inspectorInfo = debugInspectorInfo {
name = "pullRefreshIndicatorTransform"
properties["state"] = state
properties["scale"] = scale
}) {
var height by remember { mutableIntStateOf(0) }
Modifier
.onSizeChanged { height = it.height }
.graphicsLayer {
translationY = state.position - height
if (scale && !state.refreshing) {
val scaleFraction = LinearOutSlowInEasing
.transform(state.position / state.threshold)
.coerceIn(0f, 1f)
scaleX = scaleFraction
scaleY = scaleFraction
}
}
}
/**
* PullRefresh modifier to be used in conjunction with [PullRefreshState]. Provides a connection
* to the nested scroll system. Based on Android's SwipeRefreshLayout.
* @param state The [PullRefreshState] associated with this pull-to-refresh component.
* The state will be updated by this modifier.
* @param enabled If not enabled, all scroll delta and fling velocity will be ignored.
*/
fun Modifier.pullRefresh(
state: PullRefreshState, enabled: Boolean = true
) = inspectable(inspectorInfo = debugInspectorInfo {
name = "pullRefresh"
properties["state"] = state
properties["enabled"] = enabled
}) {
Modifier.pullRefresh(state::onPull, { state.onRelease() }, enabled)
}
/**
* A modifier for building pull-to-refresh components. Provides a connection to the nested scroll
* system.
* @param onPull Callback for dispatching vertical scroll delta, takes float pullDelta as argument.
* Positive delta (pulling down) is dispatched only if the child does not consume it (i.e. pulling
* down despite being at the top of a scrollable component), whereas negative delta (swiping up) is
* dispatched first (in case it is needed to push the indicator back up), and then whatever is not
* consumed is passed on to the child.
* @param onRelease Callback for when drag is released, takes float flingVelocity as argument.
* @param enabled If not enabled, all scroll delta and fling velocity will be ignored and neither
* [onPull] nor [onRelease] will be invoked.
*/
fun Modifier.pullRefresh(
onPull: (pullDelta: Float) -> Float, onRelease: suspend (flingVelocity: Float) -> Unit, enabled: Boolean = true
) = inspectable(inspectorInfo = debugInspectorInfo {
name = "pullRefresh"
properties["onPull"] = onPull
properties["onRelease"] = onRelease
properties["enabled"] = enabled
}) {
Modifier.nestedScroll(PullRefreshNestedScrollConnection(onPull, onRelease, enabled))
}
private class PullRefreshNestedScrollConnection(
private val onPull: (pullDelta: Float) -> Float,
private val onRelease: suspend (flingVelocity: Float) -> Unit,
private val enabled: Boolean
) : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset = when {
!enabled -> Offset.Zero
source == Drag && available.y < 0 -> Offset(0f, onPull(available.y)) // Swiping up
else -> Offset.Zero
}
override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset = when {
!enabled -> Offset.Zero
source == Drag && available.y > 0 -> Offset(0f, onPull(available.y)) // Pulling down
else -> Offset.Zero
}
override suspend fun onPreFling(available: Velocity): Velocity {
onRelease(available.y)
return Velocity.Zero
}
}

Wyświetl plik

@ -1,184 +0,0 @@
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.rtbishop.look4sat.presentation.components
import androidx.compose.animation.core.animate
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import kotlin.math.abs
import kotlin.math.pow
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
/**
* Creates a [PullRefreshState] that is remembered across compositions.
* Changes to [refreshing] will result in [PullRefreshState] being updated.
* @param refreshing A boolean representing whether a refresh is currently occurring.
* @param onRefresh The function to be called to trigger a refresh.
* @param refreshThreshold The threshold below which, if a release
* occurs, [onRefresh] will be called.
* @param refreshingOffset The offset at which the indicator will be drawn while refreshing. This
* offset corresponds to the position of the bottom of the indicator.
*/
@Composable
fun rememberPullRefreshState(
refreshing: Boolean,
onRefresh: () -> Unit,
refreshThreshold: Dp = PullRefreshDefaults.RefreshThreshold,
refreshingOffset: Dp = PullRefreshDefaults.RefreshingOffset,
): PullRefreshState {
require(refreshThreshold > 0.dp) { "The refresh trigger must be greater than zero!" }
val scope = rememberCoroutineScope()
val onRefreshState = rememberUpdatedState(onRefresh)
val thresholdPx: Float
val refreshingOffsetPx: Float
with(LocalDensity.current) {
thresholdPx = refreshThreshold.toPx()
refreshingOffsetPx = refreshingOffset.toPx()
}
// refreshThreshold and refreshingOffset should not be changed after instantiation, so any
// changes to these values are ignored.
val state = remember(scope) { PullRefreshState(scope, onRefreshState, refreshingOffsetPx, thresholdPx) }
SideEffect { state.setRefreshing(refreshing) }
return state
}
/**
* A state object that can be used in conjunction with [pullRefresh] to add pull-to-refresh
* behaviour to a scroll component. Based on Android's SwipeRefreshLayout.
*
* Provides [progress], a float representing how far the user has pulled as a percentage of the
* refreshThreshold. Values of one or less indicate that the user has not yet pulled past the
* threshold. Values greater than one indicate how far past the threshold the user has pulled.
*
* Can be used in conjunction with [pullRefreshIndicatorTransform] to implement Android-like
* pull-to-refresh behaviour with a custom indicator.
*
* Should be created using [rememberPullRefreshState].
*/
class PullRefreshState internal constructor(
private val animationScope: CoroutineScope,
private val onRefreshState: State<() -> Unit>,
private val refreshingOffset: Float,
internal val threshold: Float
) {
/**
* A float representing how far the user has pulled as a percentage of the refreshThreshold.
*
* If the component has not been pulled at all, progress is zero. If the pull has reached
* halfway to the threshold, progress is 0.5f. A value greater than 1 indicates that pull has
* gone beyond the refreshThreshold - e.g. a value of 2f indicates that the user has pulled to
* two times the refreshThreshold.
*/
val progress get() = adjustedDistancePulled / threshold
internal val refreshing get() = _refreshing
internal val position get() = _position
private val adjustedDistancePulled by derivedStateOf { distancePulled * DRAG_MULTIPLIER }
private var _refreshing by mutableStateOf(false)
private var _position by mutableFloatStateOf(0f)
private var distancePulled by mutableFloatStateOf(0f)
internal fun onPull(pullDelta: Float): Float {
if (this._refreshing) return 0f // Already refreshing, do nothing.
val newOffset = (distancePulled + pullDelta).coerceAtLeast(0f)
val dragConsumed = newOffset - distancePulled
distancePulled = newOffset
_position = calculateIndicatorPosition()
return dragConsumed
}
internal fun onRelease() {
if (!this._refreshing) {
if (adjustedDistancePulled > threshold) {
onRefreshState.value()
} else {
animateIndicatorTo(0f)
}
}
distancePulled = 0f
}
internal fun setRefreshing(refreshing: Boolean) {
if (this._refreshing != refreshing) {
this._refreshing = refreshing
this.distancePulled = 0f
animateIndicatorTo(if (refreshing) refreshingOffset else 0f)
}
}
private fun animateIndicatorTo(offset: Float) = animationScope.launch {
animate(initialValue = _position, targetValue = offset) { value, _ ->
_position = value
}
}
private fun calculateIndicatorPosition(): Float = when {
// If drag hasn't gone past the threshold, the position is the adjustedDistancePulled.
adjustedDistancePulled <= threshold -> adjustedDistancePulled
else -> {
// How far beyond the threshold pull has gone, as a percentage of the threshold.
val overshootPercent = abs(progress) - 1.0f
// Limit the overshoot to 200%. Linear between 0 and 200.
val linearTension = overshootPercent.coerceIn(0f, 2f)
// Non-linear tension. Increases with linearTension, but at a decreasing rate.
val tensionPercent = linearTension - linearTension.pow(2) / 4
// The additional offset beyond the threshold.
val extraOffset = threshold * tensionPercent
threshold + extraOffset
}
}
}
/**
* Default parameter values for [rememberPullRefreshState].
*/
object PullRefreshDefaults {
/**
* If the indicator is below this threshold offset when it is released, a refresh
* will be triggered.
*/
val RefreshThreshold = 80.dp
/**
* The offset at which the indicator should be rendered whilst a refresh is occurring.
*/
val RefreshingOffset = 56.dp
}
/**
* The distance pulled is multiplied by this value to give us the adjusted distance pulled, which
* is used in calculating the indicator position (when the adjusted distance pulled is less than
* the refresh threshold, it is the indicator position, otherwise the indicator position is
* derived from the progress).
*/
private const val DRAG_MULTIPLIER = 0.5f

Wyświetl plik

@ -3,7 +3,6 @@ package com.rtbishop.look4sat.presentation.passes
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@ -15,11 +14,15 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -38,12 +41,8 @@ import com.rtbishop.look4sat.domain.predict.OrbitalPass
import com.rtbishop.look4sat.presentation.MainTheme
import com.rtbishop.look4sat.presentation.components.CardIcon
import com.rtbishop.look4sat.presentation.components.NextPassRow
import com.rtbishop.look4sat.presentation.components.PullRefreshIndicator
import com.rtbishop.look4sat.presentation.components.PullRefreshState
import com.rtbishop.look4sat.presentation.components.TimerBar
import com.rtbishop.look4sat.presentation.components.TimerRow
import com.rtbishop.look4sat.presentation.components.pullRefresh
import com.rtbishop.look4sat.presentation.components.rememberPullRefreshState
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
@ -54,7 +53,6 @@ private val sdfTime = SimpleDateFormat("HH:mm:ss", Locale.ENGLISH)
@Composable
fun PassesScreen(uiState: PassesState, navToRadar: (Int, Long) -> Unit) {
val refreshPasses = { uiState.takeAction(PassesAction.RefreshPasses) }
val refreshState = rememberPullRefreshState(refreshing = uiState.isRefreshing, onRefresh = refreshPasses)
val showPassesDialog = { uiState.takeAction(PassesAction.TogglePassesDialog) }
val showRadiosDialog = { uiState.takeAction(PassesAction.ToggleRadiosDialog) }
if (uiState.isPassesDialogShown) {
@ -74,31 +72,47 @@ fun PassesScreen(uiState: PassesState, navToRadar: (Int, Long) -> Unit) {
CardIcon(onClick = { showRadiosDialog() }, iconId = R.drawable.ic_satellite)
}
NextPassRow(pass = uiState.nextPass)
PassesList(refreshState, uiState.isRefreshing, uiState.itemsList, navToRadar)
PassesList(uiState.isRefreshing, uiState.itemsList, navToRadar, refreshPasses)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun PassesList(
refreshState: PullRefreshState,
isRefreshing: Boolean,
passes: List<OrbitalPass>,
navToRadar: (Int, Long) -> Unit
navToRadar: (Int, Long) -> Unit,
refreshPasses: () -> Unit
) {
val backgroundColor = MaterialTheme.colorScheme.primary
val refreshState = rememberPullToRefreshState()
ElevatedCard(modifier = Modifier.fillMaxSize()) {
Box(modifier = Modifier.pullRefresh(refreshState), contentAlignment = Alignment.TopCenter) {
PullToRefreshBox(
isRefreshing = isRefreshing,
state = refreshState,
onRefresh = refreshPasses,
indicator = {
PullToRefreshDefaults.Indicator(
state = refreshState,
isRefreshing = isRefreshing,
color = MaterialTheme.colorScheme.background,
containerColor = MaterialTheme.colorScheme.primary,
modifier = Modifier.align(Alignment.TopCenter),
)
}
) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
items(items = passes, key = { item -> item.catNum + item.aosTime }) { pass ->
PassItem(pass = pass, navToRadar = navToRadar, modifier = Modifier.animateItem())
PassItem(
pass = pass,
navToRadar = navToRadar,
modifier = Modifier.animateItem()
)
}
}
PullRefreshIndicator(refreshing = isRefreshing, state = refreshState, backgroundColor = backgroundColor)
}
}
}
@Preview(showBackground = true)
@Composable
private fun DeepSpacePassPreview() {
@ -230,6 +244,7 @@ private fun PassItem(pass: OrbitalPass, navToRadar: (Int, Long) -> Unit, modifie
)
LinearProgressIndicator(
progress = { if (pass.isDeepSpace) 100f else pass.progress },
drawStopIndicator = {},
modifier = modifier.fillMaxWidth(0.75f)
)
Text(

Wyświetl plik

@ -1,17 +1,16 @@
[versions]
android-gradle-plugin = "8.7.2"
google-ksp = "2.0.20-1.0.25"
android-gradle-plugin = "8.7.3"
google-ksp = "2.0.21-1.0.26"
kotlin = "2.0.21"
androidx-core-ktx = "1.15.0"
androidx-core-splashscreen = "1.0.1"
androidx-room = "2.6.1"
composeBom = "2024.10.01"
compose-bom = "2024.11.00"
compose-activity = "1.9.3"
compose-lifecycle = "2.8.7"
compose-material3 = "1.3.1"
compose-navigation = "2.8.3"
compose-navigation = "2.8.4"
other-coroutines = "1.9.0"
other-json = "20240303"
@ -31,14 +30,14 @@ androidx-room = { module = "androidx.room:room-ktx", version.ref = "androidx-roo
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "androidx-room" }
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "androidx-room" }
compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
compose-animation = { group = "androidx.compose.animation", name = "animation" }
compose-material3 = { group = "androidx.compose.material3", name = "material3" }
compose-runtime = { group = "androidx.compose.runtime", name = "runtime" }
compose-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
compose-activity = { module = "androidx.activity:activity-compose", version.ref = "compose-activity" }
compose-lifecycle = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "compose-lifecycle" }
compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "compose-material3" }
compose-navigation = { module = "androidx.navigation:navigation-compose", version.ref = "compose-navigation" }
compose-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "compose-lifecycle" }
@ -67,9 +66,9 @@ kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
[bundles]
composeAll = [
"compose-bom", "compose-animation", "compose-runtime", "compose-tooling", "compose-activity",
"compose-animation", "compose-runtime", "compose-tooling", "compose-activity",
"compose-lifecycle", "compose-material3", "compose-navigation", "compose-viewmodel"
]
composeDebug = ["compose-bom", "compose-debug-manifest", "compose-debug-tooling"]
composeDebug = ["compose-debug-manifest", "compose-debug-tooling"]
unitTest = ["test-coroutines", "test-junit4", "test-mockk"]
androidTest = ["androidTest-junit", "androidTest-espresso"]

Wyświetl plik

@ -1,5 +1,3 @@
@file:Suppress("UnstableApiUsage")
pluginManagement {
repositories {
google()
@ -7,6 +5,9 @@ pluginManagement {
gradlePluginPortal()
}
}
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version ("0.8.0")
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
@ -14,6 +15,5 @@ dependencyResolutionManagement {
mavenCentral()
}
}
rootProject.name = "Look4Sat"
include(":app", ":data", ":domain")