kopia lustrzana https://github.com/rt-bishop/Look4Sat
Added compose pull to refresh for passes list
rodzic
987c735b02
commit
464ba1f4fc
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
Ładowanie…
Reference in New Issue