Added compose pull to refresh for passes list

Arty Bishop 2023-02-25 12:32:24 +00:00
rodzic 987c735b02
commit 464ba1f4fc
11 zmienionych plików z 157 dodań i 44 usunięć

Wyświetl plik

@ -89,6 +89,7 @@ dependencies {
implementation "androidx.activity:activity-compose:$activity_compose_version"
implementation "androidx.compose.animation:animation:$compose_version"
implementation "androidx.compose.compiler:compiler:$compose_compiler_version"
implementation "androidx.compose.material:material:$material_version"
implementation "androidx.compose.material3:material3:$material3_version"
implementation "androidx.compose.runtime:runtime:$compose_version"
implementation "androidx.compose.runtime:runtime-livedata:$compose_version"

Wyświetl plik

@ -11,17 +11,16 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.Interaction
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ElevatedButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.scale
@ -40,6 +39,13 @@ fun CardButton(onClick: () -> Unit, text: String, modifier: Modifier = Modifier)
) { Text(text = text, fontSize = 17.sp) }
}
@Composable
fun CardLoadingIndicator() {
Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) {
CircularProgressIndicator(modifier = Modifier.size(80.dp))
}
}
@Composable
fun RadarPing() {
val pingColor = MaterialTheme.colorScheme.primary

Wyświetl plik

@ -29,6 +29,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.domain.model.DataState
import com.rtbishop.look4sat.domain.model.SatItem
import com.rtbishop.look4sat.presentation.CardLoadingIndicator
import com.rtbishop.look4sat.presentation.MainTheme
import com.rtbishop.look4sat.presentation.onClick
import com.rtbishop.look4sat.presentation.passesScreen.PassesViewModel
@ -229,11 +230,7 @@ fun EntriesCard(state: DataState<List<SatItem>>, onSelected: (List<Int>, Boolean
}
}
}
else -> {
Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) {
CircularProgressIndicator(modifier = Modifier.size(80.dp))
}
}
else -> CardLoadingIndicator()
}
}
}

Wyświetl plik

@ -1,3 +1,5 @@
@file:OptIn(ExperimentalMaterialApi::class)
package com.rtbishop.look4sat.presentation.passesScreen
import androidx.compose.foundation.ExperimentalFoundationApi
@ -5,6 +7,11 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.PullRefreshState
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.livedata.observeAsState
@ -16,7 +23,7 @@ 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
import androidx.compose.ui.unit.*
import androidx.hilt.navigation.compose.hiltViewModel
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.domain.model.DataState
@ -29,11 +36,52 @@ import java.util.*
private val sdf = SimpleDateFormat("HH:mm:ss", Locale.ENGLISH)
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun PassesScreen(viewModel: PassesViewModel = hiltViewModel()) {
val state = viewModel.passes.observeAsState(DataState.Loading)
val state = viewModel.passes.observeAsState()
val timerText = viewModel.timerText.observeAsState()
val isRefreshing = state.value is DataState.Loading
val refreshState = rememberPullRefreshState(
refreshing = isRefreshing,
onRefresh = { viewModel.calculatePasses() })
val value = state.value
var passes = emptyList<SatPass>()
if (value is DataState.Success) passes = value.data
Column(modifier = Modifier.padding(6.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) {
PassesCard(state = state.value)
ElevatedCard(modifier = Modifier.height(52.dp)) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxSize()
) {
IconButton(onClick = {}) {
Icon(
painter = painterResource(id = R.drawable.ic_filter),
contentDescription = null
)
}
Column(
verticalArrangement = Arrangement.SpaceEvenly, modifier = Modifier.weight(1f)
) {
Text(text = timerText.value?.first ?: "Null")
Text(text = timerText.value?.second ?: "Null")
}
Text(
text = timerText.value?.third ?: "00:00:00",
fontSize = 36.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary,
textAlign = TextAlign.End,
modifier = Modifier
.weight(1f)
.padding(bottom = 2.dp, end = 8.dp)
)
}
}
PassesCard(refreshState, isRefreshing, passes)
}
}
@ -51,7 +99,7 @@ private fun PassPreview() {
@Composable
private fun Pass(pass: SatPass, modifier: Modifier = Modifier) {
Surface(color = MaterialTheme.colorScheme.background, modifier = modifier) {
Surface(modifier = Modifier.padding(bottom = 1.dp)) {
Surface(modifier = Modifier.padding(bottom = 2.dp)) {
Column(
verticalArrangement = Arrangement.spacedBy(2.dp),
modifier = Modifier
@ -60,8 +108,8 @@ private fun Pass(pass: SatPass, modifier: Modifier = Modifier) {
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = "Id:${pass.catNum} - ",
modifier = Modifier.width(90.dp),
text = "Id:${pass.catNum} - ",
modifier = Modifier.width(82.dp),
textAlign = TextAlign.End,
color = MaterialTheme.colorScheme.primary
)
@ -91,13 +139,20 @@ private fun Pass(pass: SatPass, modifier: Modifier = Modifier) {
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth()
) {
Text(text = stringResource(id = R.string.pass_aosAz, pass.aosAzimuth))
Text(
text = stringResource(id = R.string.pass_aosAz, pass.aosAzimuth),
fontSize = 15.sp
)
Text(
text = stringResource(id = R.string.pass_altitude, pass.altitude),
textAlign = TextAlign.Center,
fontSize = 15.sp,
modifier = Modifier.weight(1f)
)
Text(text = stringResource(id = R.string.pass_losAz, pass.losAzimuth))
Text(
text = stringResource(id = R.string.pass_losAz, pass.losAzimuth),
fontSize = 15.sp
)
}
Row(
verticalAlignment = Alignment.CenterVertically,
@ -105,14 +160,22 @@ private fun Pass(pass: SatPass, modifier: Modifier = Modifier) {
modifier = Modifier.fillMaxWidth()
) {
val deepTime = stringResource(id = R.string.pass_placeholder)
Text(text = if (pass.isDeepSpace) deepTime else sdf.format(Date(pass.aosTime)))
Text(
text = if (pass.isDeepSpace) deepTime else sdf.format(Date(pass.aosTime)),
fontSize = 15.sp
)
LinearProgressIndicator(
progress = pass.progress,
modifier = modifier.weight(1f),
progress = if (pass.isDeepSpace) 1f else pass.progress,
modifier = modifier
.fillMaxWidth(0.75f)
.padding(top = 3.dp),
color = MaterialTheme.colorScheme.primary,
trackColor = MaterialTheme.colorScheme.inverseSurface
)
Text(text = if (pass.isDeepSpace) deepTime else sdf.format(Date(pass.losTime)))
Text(
text = if (pass.isDeepSpace) deepTime else sdf.format(Date(pass.losTime)),
fontSize = 15.sp
)
}
}
}
@ -121,23 +184,22 @@ private fun Pass(pass: SatPass, modifier: Modifier = Modifier) {
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun PassesCard(state: DataState<List<SatPass>>) {
private fun PassesCard(
refreshState: PullRefreshState, isRefreshing: Boolean, passes: List<SatPass>
) {
ElevatedCard(modifier = Modifier.fillMaxSize()) {
when (state) {
is DataState.Success -> {
LazyColumn {
items(
items = state.data,
key = { item -> item.catNum + item.aosTime }) { pass ->
Pass(pass, Modifier.animateItemPlacement())
}
}
}
else -> {
Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) {
CircularProgressIndicator(modifier = Modifier.size(80.dp))
Box(Modifier.pullRefresh(refreshState)) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
items(items = passes, key = { item -> item.catNum + item.aosTime }) { pass ->
Pass(pass, Modifier.animateItemPlacement())
}
}
PullRefreshIndicator(
refreshing = isRefreshing,
state = refreshState,
modifier = Modifier.align(Alignment.TopCenter),
backgroundColor = MaterialTheme.colorScheme.primary
)
}
}
}

Wyświetl plik

@ -23,6 +23,7 @@ import com.rtbishop.look4sat.domain.ISatelliteManager
import com.rtbishop.look4sat.domain.ISettingsManager
import com.rtbishop.look4sat.domain.model.DataState
import com.rtbishop.look4sat.domain.predict.SatPass
import com.rtbishop.look4sat.utility.toTimerString
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.*
import javax.inject.Inject
@ -34,10 +35,13 @@ class PassesViewModel @Inject constructor(
private val settings: ISettingsManager
) : ViewModel() {
private val _passes = MutableLiveData<DataState<List<SatPass>>>()
private val timerData = Triple("Next - Id:Null", "Name: Null", "00:00:00")
private val _timerText = MutableLiveData<Triple<String, String, String>>()
private var passesProcessing: Job? = null
private val _passes = MutableLiveData<DataState<List<SatPass>>>(DataState.Loading)
val passes: LiveData<DataState<List<SatPass>>> = _passes
val entriesTotal: LiveData<Int> = repository.getEntriesTotal().asLiveData()
val passes: LiveData<DataState<List<SatPass>>> = _passes
val timerText: LiveData<Triple<String, String, String>> = _timerText
init {
viewModelScope.launch {
@ -45,8 +49,27 @@ class PassesViewModel @Inject constructor(
passesProcessing?.cancelAndJoin()
passesProcessing = viewModelScope.launch {
while (isActive) {
val time = System.currentTimeMillis()
val newPasses = satelliteManager.processPasses(passes, time)
val timeNow = System.currentTimeMillis()
val newPasses = satelliteManager.processPasses(passes, timeNow)
if (newPasses.isNotEmpty()) {
try {
val nextPass = newPasses.first { it.aosTime.minus(timeNow) > 0 }
val catNum = nextPass.catNum
val name = nextPass.name
val millisBeforeStart = nextPass.aosTime.minus(timeNow)
val timerString = millisBeforeStart.toTimerString()
_timerText.postValue(Triple("Next - Id:$catNum", name, timerString))
} catch (e: NoSuchElementException) {
val lastPass = newPasses.last()
val catNum = lastPass.catNum
val name = lastPass.name
val millisBeforeEnd = lastPass.losTime.minus(timeNow)
val timerString = millisBeforeEnd.toTimerString()
_timerText.postValue(Triple("Next - Id:$catNum", name, timerString))
}
} else _timerText.postValue(timerData)
_passes.postValue(DataState.Success(newPasses))
delay(1000)
}
@ -71,6 +94,7 @@ class PassesViewModel @Inject constructor(
) {
viewModelScope.launch {
_passes.postValue(DataState.Loading)
// _timerText.postValue(timerDefaultText)
passesProcessing?.cancelAndJoin()
selection?.let { items -> settings.saveEntriesSelection(items) }
settings.setHoursAhead(hoursAhead)
@ -78,7 +102,9 @@ class PassesViewModel @Inject constructor(
val stationPos = settings.loadStationPosition()
val selectedIds = settings.loadEntriesSelection()
val satellites = repository.getEntriesWithIds(selectedIds)
satelliteManager.calculatePasses(satellites, stationPos, timeRef, hoursAhead, minElevation)
satelliteManager.calculatePasses(
satellites, stationPos, timeRef, hoursAhead, minElevation
)
}
}
}

Wyświetl plik

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#000000"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M10,18h4v-2h-4v2zM3,6v2h18L21,6L3,6zM6,13h12v-2L6,11v2z" />
</vector>

Wyświetl plik

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#000000"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M4.25,5.61C6.27,8.2 10,13 10,13v6c0,0.55 0.45,1 1,1h2c0.55,0 1,-0.45 1,-1v-6c0,0 3.72,-4.8 5.74,-7.39C20.25,4.95 19.78,4 18.95,4H5.04C4.21,4 3.74,4.95 4.25,5.61z" />
</vector>

Wyświetl plik

@ -80,7 +80,7 @@
<string name="pass_elevation">Элевация: %.1f°</string>
<string name="pass_losAz">%.1f° - КПС</string>
<string name="pass_altitude">Высота: %d км</string>
<string name="pass_placeholder"> - - : - - </string>
<string name="pass_placeholder"> -- : -- : -- </string>
<string name="pass_startTime">HH:mm:ss - EEE</string>
<string name="pass_endTime">EEE - HH:mm:ss</string>

Wyświetl plik

@ -80,7 +80,7 @@
<string name="pass_elevation">仰角:%.1f°</string>
<string name="pass_losAz">%.1f° - 出境</string>
<string name="pass_altitude">高度:%d km</string>
<string name="pass_placeholder">" - - : - - "</string>
<string name="pass_placeholder"> -- : -- : -- </string>
<string name="pass_startTime">HH:mm:ss - EEE</string>
<string name="pass_endTime">EEE - HH:mm:ss</string>

Wyświetl plik

@ -80,7 +80,7 @@
<string name="pass_elevation">Elevation: %.1f°</string>
<string name="pass_losAz">%.1f° - LOS</string>
<string name="pass_altitude">Altitude: %d km</string>
<string name="pass_placeholder"> - - : - - </string>
<string name="pass_placeholder"> -- : -- : -- </string>
<string name="pass_startTime">HH:mm:ss - EEE</string>
<string name="pass_endTime">EEE - HH:mm:ss</string>

Wyświetl plik

@ -16,8 +16,9 @@ buildscript {
osmdroid_version = '6.1.14'
json_version = '20220924'
compose_version = '1.3.3'
compose_compiler_version = '1.4.2'
compose_compiler_version = '1.4.3'
activity_compose_version = '1.6.1'
material_version = '1.3.1'
material3_version = '1.0.1'
leakcanary_version = '2.10'
junit_version = '4.13.2'