Minor fixes for entries, settings and maps

pull/122/head
Arty Bishop 2023-03-26 15:45:07 +01:00
rodzic d3408280cf
commit 582ae144b1
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 5C71CFDC37AD73CC
10 zmienionych plików z 149 dodań i 134 usunięć

Wyświetl plik

@ -26,6 +26,7 @@ import com.rtbishop.look4sat.framework.data.remote.NetworkSource
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import org.osmdroid.config.Configuration
class MainContainer(private val context: Context) {
@ -70,6 +71,7 @@ class MainContainer(private val context: Context) {
private fun provideSettingsRepository(): ISettingsRepository {
val manager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
val preferences = context.getSharedPreferences("default", Context.MODE_PRIVATE)
Configuration.getInstance().load(context, preferences)
return SettingsRepository(manager, preferences)
}
}

Wyświetl plik

@ -124,15 +124,17 @@ class SettingsRepository(
//endregion
override fun saveEntriesSelection(catnums: List<Int>) {
val stringList = catnums.map { catnum -> catnum.toString() }
preferences.edit { putStringSet(keySelection, stringList.toSet()) }
private val _satelliteSelection = MutableStateFlow(loadEntriesSelection())
override val satelliteSelection: StateFlow<List<Int>> = _satelliteSelection
override fun saveEntriesSelection(catnums: List<Int>) = preferences.edit {
putStringSet(keySelection, catnums.map { catnum -> catnum.toString() }.toSet())
_satelliteSelection.value = catnums
}
override fun loadEntriesSelection(): List<Int> {
val catnums =
preferences.getStringSet(keySelection, emptySet())?.map { catnum -> catnum.toInt() }
return catnums?.sorted() ?: emptyList()
private fun loadEntriesSelection(): List<Int> {
val catNums = preferences.getStringSet(keySelection, emptySet())
return catNums?.map { catnum -> catnum.toInt() }?.sorted() ?: emptyList()
}
override fun saveSatType(type: String, catnums: List<Int>) {

Wyświetl plik

@ -10,12 +10,19 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.material3.*
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ElevatedButton
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.flow.Flow
@ -31,6 +38,16 @@ fun CardButton(onClick: () -> Unit, text: String, modifier: Modifier = Modifier)
) { Text(text = text, fontSize = 17.sp) }
}
@Composable
fun CardIcon(onClick: () -> Unit, iconId: Int, description: String? = null) {
val clickableModifier = Modifier.clickable { onClick() }
ElevatedCard(modifier = Modifier.size(48.dp)) {
Box(modifier = clickableModifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Icon(painter = painterResource(id = iconId), contentDescription = description)
}
}
}
@Composable
fun CardLoadingIndicator() {
Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) {

Wyświetl plik

@ -6,6 +6,7 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
@ -13,8 +14,10 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
@ -25,6 +28,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.model.DataState
import com.rtbishop.look4sat.model.SatItem
import com.rtbishop.look4sat.presentation.CardIcon
import com.rtbishop.look4sat.presentation.CardLoadingIndicator
import com.rtbishop.look4sat.presentation.MainTheme
import com.rtbishop.look4sat.presentation.dialogs.TypesDialog
@ -50,12 +54,7 @@ fun EntriesScreen(navToPasses: () -> Unit) {
viewModel.saveSelection()
navToPasses()
})
Row(horizontalArrangement = Arrangement.spacedBy(6.dp), modifier = Modifier.height(48.dp)) {
EntryTypeCard(
type = viewModel.getSatType(), { toggleDialog() }, modifier = Modifier.weight(1f)
)
SelectionCard(unselectAll = { unselectAll() }, selectAll = { selectAll() })
}
MiddleBar(viewModel.getSatType(), { toggleDialog() }, { unselectAll() }, { selectAll() })
EntriesCard(state = state.value) { list, value -> viewModel.updateSelection(list, value) }
}
}
@ -64,83 +63,81 @@ fun EntriesScreen(navToPasses: () -> Unit) {
@Composable
private fun TopBarPreview() = MainTheme { TopBar({}, {}) }
@Preview(showBackground = true)
@Composable
private fun MiddleBarPreview() = MainTheme { MiddleBar({}, {}) }
@Preview(showBackground = true)
@Composable
private fun EntryPreview() {
val satItem = SatItem(45555, "SatName", emptyList(), true)
MainTheme { Entry(item = satItem, onSelected = { _, _ -> run {} }, modifier = Modifier) }
}
@Preview(showBackground = true)
@Composable
private fun EntriesPreview() {
MainTheme { EntriesCard(DataState.Loading) { _, _ -> run {} } }
}
@Composable
private fun TopBar(setQuery: (String) -> Unit, saveSelection: () -> Unit) {
Row(horizontalArrangement = Arrangement.spacedBy(6.dp), modifier = Modifier.height(56.dp)) {
Row(horizontalArrangement = Arrangement.spacedBy(6.dp), modifier = Modifier.height(48.dp)) {
SearchBar(setQuery = { setQuery(it) }, modifier = Modifier.weight(1f))
SaveButton(saveSelection = { saveSelection() }, modifier = Modifier.height(56.dp))
SaveButton(saveSelection = { saveSelection() }, modifier = Modifier.height(48.dp))
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun SearchBar(setQuery: (String) -> Unit, modifier: Modifier = Modifier) {
val currentQuery = rememberSaveable { mutableStateOf("") }
OutlinedTextField(
value = currentQuery.value,
onValueChange = { newValue ->
currentQuery.value = newValue
setQuery(newValue)
},
maxLines = 1,
// label = { Text(text = stringResource(id = R.string.entries_search_hint)) },
placeholder = { Text(text = stringResource(id = R.string.entries_search_hint)) },
leadingIcon = {
Icon(
painter = painterResource(id = R.drawable.ic_search),
contentDescription = null
val setNewQuery = { newValue: String ->
currentQuery.value = newValue
setQuery(newValue)
}
ElevatedCard(modifier = modifier.height(48.dp)) {
Row(
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(start = 12.dp)
) {
Icon(painter = painterResource(id = R.drawable.ic_search), contentDescription = null)
BasicTextField(
value = currentQuery.value,
onValueChange = { setNewQuery(it) },
singleLine = true,
modifier = modifier.padding(start = 12.dp),
textStyle = TextStyle(
fontSize = 16.sp,
fontWeight = FontWeight.Medium,
color = MaterialTheme.colorScheme.onSurface
),
decorationBox = { innerTextField ->
if (currentQuery.value.isEmpty()) {
Text(
text = stringResource(id = R.string.entries_search_hint),
fontSize = 16.sp,
fontWeight = FontWeight.Normal,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
innerTextField()
},
cursorBrush = SolidColor(MaterialTheme.colorScheme.onSurface)
)
},
trailingIcon = {
Icon(painter = painterResource(id = R.drawable.ic_close),
contentDescription = null,
modifier = Modifier.onClick {
currentQuery.value = ""
setQuery("")
})
},
shape = MaterialTheme.shapes.medium,
colors = TextFieldDefaults.outlinedTextFieldColors(
containerColor = MaterialTheme.colorScheme.surface,
focusedBorderColor = MaterialTheme.colorScheme.onSurfaceVariant,
unfocusedBorderColor = MaterialTheme.colorScheme.onSurfaceVariant
),
modifier = modifier
)
IconButton(onClick = { setNewQuery("") }) {
Icon(painter = painterResource(id = R.drawable.ic_close), contentDescription = null)
}
}
}
}
@Composable
private fun SaveButton(saveSelection: () -> Unit, modifier: Modifier = Modifier) {
ElevatedButton(
onClick = { saveSelection() }, colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.onPrimary
), shape = MaterialTheme.shapes.medium, modifier = modifier.width(96.dp)
) { Icon(painter = painterResource(id = R.drawable.ic_checkmark), contentDescription = null) }
val clickableModifier = modifier.clickable { saveSelection() }
ElevatedCard(
modifier = Modifier.width(102.dp),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primary)
) {
Box(modifier = clickableModifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Icon(painter = painterResource(id = R.drawable.ic_checkmark), contentDescription = null)
}
}
}
@Preview(showBackground = true)
@Composable
private fun MiddleBar(unselectAll: () -> Unit, selectAll: () -> Unit) {
private fun MiddleBarPreview() = MainTheme { MiddleBar("Amateur", {}, {}, {}) }
@Composable
private fun MiddleBar(type: String, navigate: () -> Unit, uncheck: () -> Unit, check: () -> Unit) {
Row(horizontalArrangement = Arrangement.spacedBy(6.dp), modifier = Modifier.height(48.dp)) {
EntryTypeCard(type = "Amateur", {}, modifier = Modifier.weight(1f))
SelectionCard(unselectAll = { unselectAll() }, selectAll = { selectAll() })
EntryTypeCard(type = type, { navigate() }, modifier = Modifier.weight(1f))
CardIcon(onClick = { uncheck() }, iconId = R.drawable.ic_checkbox_off)
CardIcon(onClick = { check() }, iconId = R.drawable.ic_checkbox_on)
}
}
@ -163,27 +160,17 @@ private fun EntryTypeCard(type: String, onClick: () -> Unit, modifier: Modifier
text = "Type: $type",
fontSize = 16.sp,
fontWeight = FontWeight.Medium,
modifier = modifier.padding(start = 16.dp, end = 6.dp, bottom = 2.dp)
modifier = modifier.padding(start = 12.dp, end = 6.dp, bottom = 2.dp)
)
}
}
}
@Preview(showBackground = true)
@Composable
private fun SelectionCard(unselectAll: () -> Unit, selectAll: () -> Unit) {
ElevatedCard {
Row(modifier = Modifier.width(96.dp)) {
val unselectIcon = painterResource(id = R.drawable.ic_checkbox_off)
val selectIcon = painterResource(id = R.drawable.ic_checkbox_on)
val iconColor = MaterialTheme.colorScheme.onSurface
IconButton(onClick = { unselectAll() }) {
Icon(painter = unselectIcon, contentDescription = null, tint = iconColor)
}
IconButton(onClick = { selectAll() }) {
Icon(painter = selectIcon, contentDescription = null, tint = iconColor)
}
}
}
private fun EntryPreview() {
val satItem = SatItem(45555, "SatName", emptyList(), true)
MainTheme { Entry(item = satItem, onSelected = { _, _ -> run {} }, modifier = Modifier) }
}
@Composable
@ -216,6 +203,12 @@ private fun Entry(item: SatItem, onSelected: (List<Int>, Boolean) -> Unit, modif
}
}
@Preview(showBackground = true)
@Composable
private fun EntriesPreview() {
MainTheme { EntriesCard(DataState.Loading) { _, _ -> run {} } }
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun EntriesCard(state: DataState<List<SatItem>>, onSelected: (List<Int>, Boolean) -> Unit) {

Wyświetl plik

@ -24,21 +24,23 @@ import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import com.rtbishop.look4sat.MainApplication
import com.rtbishop.look4sat.domain.IDataRepository
import com.rtbishop.look4sat.domain.ISatelliteRepository
import com.rtbishop.look4sat.domain.ISettingsRepository
import com.rtbishop.look4sat.model.DataState
import com.rtbishop.look4sat.model.SatItem
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.*
@OptIn(ExperimentalCoroutinesApi::class)
class EntriesViewModel(
private val dataRepository: IDataRepository,
private val satelliteRepository: ISatelliteRepository,
private val settingsRepository: ISettingsRepository
private val settingsRepository: ISettingsRepository,
private val dispatcher: CoroutineDispatcher = Dispatchers.Default
) : ViewModel() {
private val satType = MutableStateFlow("All")
@ -67,7 +69,7 @@ class EntriesViewModel(
}
fun selectCurrentItems(selectAll: Boolean) = viewModelScope.launch {
updateSelection(itemsWithQuery.first().map { item -> item.catnum }, selectAll)
updateEntriesSelection(itemsWithQuery.first().map { item -> item.catnum }, selectAll)
}
fun setQuery(query: String) {
@ -77,40 +79,40 @@ class EntriesViewModel(
fun saveSelection() = viewModelScope.launch {
val newSelection = itemsFromRepo.value.filter { it.isSelected }.map { it.catnum }
settingsRepository.saveEntriesSelection(newSelection)
val selectedIds = settingsRepository.loadEntriesSelection()
val satellites = dataRepository.getEntriesWithIds(selectedIds)
val stationPos = settingsRepository.stationPosition.value
val timeRef = System.currentTimeMillis()
val hoursAhead = settingsRepository.getHoursAhead()
val minElev = settingsRepository.getMinElevation()
satelliteRepository.calculatePasses(satellites, stationPos, timeRef, hoursAhead, minElev)
}
fun updateSelection(catNums: List<Int>, isSelected: Boolean) {
itemsFromRepo.value.let { itemsAll ->
val copiedList = itemsAll.map { item -> item.copy() }
catNums.forEach { catnum ->
copiedList.find { item -> item.catnum == catnum }?.isSelected = isSelected
fun updateSelection(catNums: List<Int>, isSelected: Boolean) = viewModelScope.launch {
updateEntriesSelection(catNums, isSelected)
}
private suspend fun updateEntriesSelection(catNums: List<Int>, isSelected: Boolean) {
withContext(dispatcher) {
itemsFromRepo.value.let { itemsAll ->
val copiedList = itemsAll.map { item -> item.copy() }
catNums.forEach { catnum ->
copiedList.find { item -> item.catnum == catnum }?.isSelected = isSelected
}
itemsFromRepo.value = copiedList
}
itemsFromRepo.value = copiedList
}
}
private fun filterByType(items: List<SatItem>, type: String): List<SatItem> {
if (type == "All") return items
val catnums = settingsRepository.loadSatType(type)
if (catnums.isEmpty()) return items
return items.filter { item -> item.catnum in catnums }
private suspend fun filterByType(items: List<SatItem>, type: String): List<SatItem> {
return withContext(dispatcher) {
if (type == "All") return@withContext items
val catnums = settingsRepository.loadSatType(type)
if (catnums.isEmpty()) return@withContext items
return@withContext items.filter { item -> item.catnum in catnums }
}
}
private fun filterByQuery(items: List<SatItem>, query: String): List<SatItem> {
if (query.isBlank()) return items
return try {
items.filter { it.catnum == query.toInt() }
} catch (e: Exception) {
items.filter { item ->
val itemName = item.name.lowercase(Locale.getDefault())
itemName.contains(query.lowercase(Locale.getDefault()))
private suspend fun filterByQuery(items: List<SatItem>, query: String): List<SatItem> {
return withContext(dispatcher) {
if (query.isBlank()) return@withContext items
return@withContext try {
items.filter { it.catnum == query.toInt() }
} catch (e: Exception) {
items.filter { item -> item.name.lowercase().contains(query.lowercase()) }
}
}
}
@ -122,7 +124,6 @@ class EntriesViewModel(
val container = (this[applicationKey] as MainApplication).container
EntriesViewModel(
container.dataRepository,
container.satelliteRepository,
container.settingsRepository
)
}

Wyświetl plik

@ -1,6 +1,5 @@
package com.rtbishop.look4sat.presentation.map
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
@ -32,7 +31,6 @@ import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.domain.Satellite
import com.rtbishop.look4sat.model.GeoPos
import com.rtbishop.look4sat.model.SatPos
import org.osmdroid.config.Configuration
import org.osmdroid.tileprovider.tilesource.XYTileSource
import org.osmdroid.util.GeoPoint
import org.osmdroid.views.CustomZoomButtonsController
@ -66,9 +64,6 @@ private val labelRect = Rect()
@Composable
fun MapScreen() {
val viewModel = viewModel(MapViewModel::class.java, factory = MapViewModel.Factory)
val context = LocalContext.current
val preferences = context.getSharedPreferences("default", Context.MODE_PRIVATE)
Configuration.getInstance().load(context, preferences)
viewModel.selectDefaultSatellite(-1)
val stationPos = viewModel.stationPos.collectAsState(initial = null)
val positions = viewModel.positions.collectAsState(initial = null)

Wyświetl plik

@ -92,7 +92,7 @@ class MapViewModel(
fun selectDefaultSatellite(catnum: Int) {
viewModelScope.launch {
val selectedIds = settingsRepository.loadEntriesSelection()
val selectedIds = settingsRepository.satelliteSelection.value
dataRepository.getEntriesWithIds(selectedIds).also { satellites ->
if (satellites.isNotEmpty()) {
allSatellites = satellites

Wyświetl plik

@ -87,12 +87,14 @@ class PassesViewModel(
}
}
}
viewModelScope.launch {
settingsRepository.satelliteSelection.collect { calculatePasses() }
}
viewModelScope.launch {
dataRepository.updateState.collect { updateState ->
if (updateState is DataState.Success) calculatePasses()
}
}
calculatePasses()
}
fun shouldUseUTC() = settingsRepository.isUtcEnabled()
@ -100,18 +102,17 @@ class PassesViewModel(
fun calculatePasses(
hoursAhead: Int = 1,
minElevation: Double = settingsRepository.getMinElevation(),
timeRef: Long = System.currentTimeMillis(),
selection: List<Int>? = null
timeRef: Long = System.currentTimeMillis()
) {
viewModelScope.launch {
_passes.emit(DataState.Loading)
// _timerText.postValue(timerDefaultText)
passesProcessing?.cancelAndJoin()
selection?.let { items -> settingsRepository.saveEntriesSelection(items) }
// selection?.let { items -> settingsRepository.saveEntriesSelection(items) }
settingsRepository.setHoursAhead(hoursAhead)
settingsRepository.setMinElevation(minElevation)
val stationPos = settingsRepository.stationPosition.value
val selectedIds = settingsRepository.loadEntriesSelection()
val selectedIds = settingsRepository.satelliteSelection.value
val satellites = dataRepository.getEntriesWithIds(selectedIds)
satelliteRepository.calculatePasses(
satellites, stationPos, timeRef, hoursAhead, minElevation

Wyświetl plik

@ -22,9 +22,13 @@ import com.rtbishop.look4sat.domain.ISettingsRepository
import com.rtbishop.look4sat.model.DataState
import com.rtbishop.look4sat.model.SatEntry
import com.rtbishop.look4sat.model.SatItem
import kotlinx.coroutines.*
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import java.io.InputStream
import java.util.zip.ZipInputStream
@ -56,7 +60,7 @@ class DataRepository(
override suspend fun getEntriesWithIds(ids: List<Int>) = entrySource.getEntriesWithIds(ids)
override suspend fun getEntriesWithSelection(): List<SatItem> {
val selectedIds = settingsRepository.loadEntriesSelection()
val selectedIds = settingsRepository.satelliteSelection.value
return getEntriesWithModes().onEach { it.isSelected = it.catnum in selectedIds }
}

Wyświetl plik

@ -24,6 +24,8 @@ interface ISettingsRepository {
val stationPosition: StateFlow<GeoPos>
val satelliteSelection: StateFlow<List<Int>>
fun setGpsPosition(): Boolean
fun setGeoPosition(latitude: Double, longitude: Double, altitude: Double = 0.0): Boolean
@ -64,8 +66,6 @@ interface ISettingsRepository {
fun saveEntriesSelection(catnums: List<Int>)
fun loadEntriesSelection(): List<Int>
fun saveSatType(type: String, catnums: List<Int>)
fun loadSatType(type: String): List<Int>