Added sat items filtering by transmitter mode

This commit is contained in:
Arty Bishop committed 2021-03-27 16:54:03 +00:00
1 parent 995953b823
commit 8d8e0bb7ac
6 files changed
+108 -22

No files matched your search

@@ -35,6 +35,7 @@ class PrefsRepo @Inject constructor(val preferences: SharedPreferences, moshi: M
companion object { companion object {
const val keySources = "sourcesListJson" const val keySources = "sourcesListJson"
const val keyModes = "satModes"
const val keyLatitude = "latitude" const val keyLatitude = "latitude"
const val keyLongitude = "longitude" const val keyLongitude = "longitude"
const val keyAltitude = "altitude" const val keyAltitude = "altitude"
@@ -117,7 +118,7 @@ class PrefsRepo @Inject constructor(val preferences: SharedPreferences, moshi: M
val sourcesJson = sourcesAdapter.toJson(sources) val sourcesJson = sourcesAdapter.toJson(sources)
preferences.edit { putString(keySources, sourcesJson) } preferences.edit { putString(keySources, sourcesJson) }
} }
fun loadDefaultSources(): List<TleSource> { fun loadDefaultSources(): List<TleSource> {
return listOf( return listOf(
TleSource("https://celestrak.com/NORAD/elements/active.txt"), TleSource("https://celestrak.com/NORAD/elements/active.txt"),
@@ -126,4 +127,18 @@ class PrefsRepo @Inject constructor(val preferences: SharedPreferences, moshi: M
TleSource("https://www.prismnet.com/~mmccants/tles/inttles.zip") TleSource("https://www.prismnet.com/~mmccants/tles/inttles.zip")
) )
} }
fun saveModesSelection(modes: List<String>) {
val modesSet = modes.toSet()
preferences.edit {
putStringSet(keyModes, modesSet)
}
}
fun loadModesSelection(): List<String> {
preferences.getStringSet(keyModes, setOf())?.let { modesSet ->
return modesSet.toList().sorted()
}
return emptyList()
}
} }
@@ -27,6 +27,7 @@ import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.SimpleItemAnimator import androidx.recyclerview.widget.SimpleItemAnimator
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.rtbishop.look4sat.R import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.data.model.Result import com.rtbishop.look4sat.data.model.Result
@@ -71,6 +72,7 @@ class EntriesFragment : Fragment(R.layout.fragment_entries), EntriesAdapter.Entr
} }
importWeb.setOnClickListener { showImportFromWebDialog() } importWeb.setOnClickListener { showImportFromWebDialog() }
importFile.setOnClickListener { filePicker.launch("*/*") } importFile.setOnClickListener { filePicker.launch("*/*") }
selectMode.setOnClickListener { showModesDialog() }
selectAll.setOnClickListener { entriesAdapter?.selectAllItems() } selectAll.setOnClickListener { entriesAdapter?.selectAllItems() }
searchBar.addTextChangedListener { query -> filterByQuery(query) } searchBar.addTextChangedListener { query -> filterByQuery(query) }
searchBar.clearFocus() searchBar.clearFocus()
@@ -85,6 +87,7 @@ class EntriesFragment : Fragment(R.layout.fragment_entries), EntriesAdapter.Entr
setEmpty() setEmpty()
} else { } else {
entriesAdapter?.submitAllItems(result.data) entriesAdapter?.submitAllItems(result.data)
binding?.entriesRecycler?.smoothScrollToPosition(0)
setLoaded() setLoaded()
} }
} }
@@ -101,11 +104,12 @@ class EntriesFragment : Fragment(R.layout.fragment_entries), EntriesAdapter.Entr
private fun observeSourcesResult() { private fun observeSourcesResult() {
getNavResult<List<String>>(R.id.nav_entries, "sources") { result -> getNavResult<List<String>>(R.id.nav_entries, "sources") { result ->
val sources = result.map { TleSource(it) } result.map { TleSource(it) }.let { sources ->
if (sources.isNullOrEmpty()) { if (sources.isNullOrEmpty()) {
viewModel.importSatDataFromSources() viewModel.importSatDataFromSources()
} else { } else {
viewModel.importSatDataFromSources(sources) viewModel.importSatDataFromSources(sources)
}
} }
} }
} }
@@ -134,15 +138,46 @@ class EntriesFragment : Fragment(R.layout.fragment_entries), EntriesAdapter.Entr
} }
} }
private fun filterByQuery(it: Editable?) { private fun filterByQuery(query: Editable?) {
entriesAdapter?.filterItems(it.toString()) entriesAdapter?.filterItems(query.toString())
binding?.entriesRecycler?.scrollToPosition(0) binding?.entriesRecycler?.smoothScrollToPosition(0)
} }
private fun showImportFromWebDialog() { private fun showImportFromWebDialog() {
findNavController().navigate(R.id.nav_dialog_sources) findNavController().navigate(R.id.nav_dialog_sources)
} }
private fun showModesDialog() {
val modes = arrayOf(
"AFSK", "AFSK S-Net", "AFSK SALSAT", "AHRPT", "AM", "APT", "BPSK", "BPSK PMT-A3",
"CERTO", "CW", "DQPSK", "DSTAR", "DUV", "FFSK", "FM", "FMN", "FSK", "FSK AX.100 Mode 5",
"FSK AX.100 Mode 6", "FSK AX.25 G3RUH", "GFSK", "GFSK Rktr", "GMSK", "HRPT", "LoRa",
"LRPT", "LSB", "MFSK", "MSK", "MSK AX.100 Mode 5", "MSK AX.100 Mode 6", "OFDM", "OQPSK",
"PSK", "PSK31", "PSK63", "QPSK", "QPSK31", "QPSK63", "SSTV", "USB", "WSJT"
)
val savedModes = BooleanArray(modes.size)
val selectedModes = viewModel.getModesSelection().toMutableList()
selectedModes.forEach { savedModes[modes.indexOf(it)] = true }
MaterialAlertDialogBuilder(requireContext()).apply {
setTitle(getString(R.string.modes_title))
setMultiChoiceItems(modes, savedModes) { _, which, isChecked ->
if (isChecked) {
selectedModes.add(modes[which])
} else if (selectedModes.contains(modes[which])) {
selectedModes.remove(modes[which])
}
}
setPositiveButton(getString(android.R.string.ok)) { _, _ ->
viewModel.filterByModes(selectedModes)
}
setNeutralButton(getString(android.R.string.cancel)) { dialog, _ ->
dialog.dismiss()
}
create()
show()
}
}
override fun updateSelection(catNums: List<Int>, isSelected: Boolean) { override fun updateSelection(catNums: List<Int>, isSelected: Boolean) {
viewModel.updateEntriesSelection(catNums, isSelected) viewModel.updateEntriesSelection(catNums, isSelected)
} }
@@ -28,10 +28,8 @@ import com.rtbishop.look4sat.data.repository.PrefsRepo
import com.rtbishop.look4sat.data.repository.SatelliteRepo import com.rtbishop.look4sat.data.repository.SatelliteRepo
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flattenMerge import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@@ -44,31 +42,56 @@ class EntriesViewModel @Inject constructor(
private val satelliteRepo: SatelliteRepo private val satelliteRepo: SatelliteRepo
) : ViewModel() { ) : ViewModel() {
private val satDataState = MutableSharedFlow<Result<List<SatItem>>>(replay = 0) private val allSatItems = mutableListOf<SatItem>()
private val satDataFlow = satelliteRepo.getAllSatItems().map { Result.Success(it) } private val satDataState = MutableStateFlow<Result<List<SatItem>>>(Result.InProgress)
val satData = val satData = satDataState.asLiveData(viewModelScope.coroutineContext)
flowOf(satDataState, satDataFlow).flattenMerge().asLiveData(viewModelScope.coroutineContext)
init {
viewModelScope.launch {
satelliteRepo.getAllSatItems().collect { items ->
allSatItems.clear()
allSatItems.addAll(items)
filterByModes(getModesSelection())
}
}
}
fun getModesSelection(): List<String> {
return prefsRepo.loadModesSelection()
}
fun filterByModes(modes: List<String>) {
if (modes.isEmpty()) {
satDataState.value = Result.Success(allSatItems)
} else {
val itemsWithModes = allSatItems.filter { item ->
item.modes.any { mode -> mode in modes }
}
satDataState.value = Result.Success(itemsWithModes)
}
prefsRepo.saveModesSelection(modes)
}
fun importSatDataFromFile(uri: Uri) { fun importSatDataFromFile(uri: Uri) {
viewModelScope.launch { viewModelScope.launch {
satDataState.emit(Result.InProgress) satDataState.value = Result.InProgress
try { try {
satelliteRepo.importSatDataFromFile(uri) satelliteRepo.importSatDataFromFile(uri)
} catch (exception: Exception) { } catch (exception: Exception) {
satDataState.emit(Result.Error(exception)) satDataState.value = Result.Error(exception)
} }
} }
} }
fun importSatDataFromSources(sources: List<TleSource> = prefsRepo.loadDefaultSources()) { fun importSatDataFromSources(sources: List<TleSource> = prefsRepo.loadDefaultSources()) {
viewModelScope.launch { viewModelScope.launch {
satDataState.emit(Result.InProgress) satDataState.value = Result.InProgress
val updateMillis = measureTimeMillis { val updateMillis = measureTimeMillis {
try { try {
prefsRepo.saveTleSources(sources) prefsRepo.saveTleSources(sources)
satelliteRepo.importSatDataFromWeb(sources) satelliteRepo.importSatDataFromWeb(sources)
} catch (exception: Exception) { } catch (exception: Exception) {
satDataState.emit(Result.Error(exception)) satDataState.value = Result.Error(exception)
} }
} }
Timber.d("Update from WEB took $updateMillis ms") Timber.d("Update from WEB took $updateMillis ms")
+12 -1
View File
@@ -60,10 +60,21 @@
android:iconifiedByDefault="false" android:iconifiedByDefault="false"
android:importantForAutofill="no" android:importantForAutofill="no"
android:inputType="text" android:inputType="text"
app:layout_constraintEnd_toStartOf="@+id/select_all" app:layout_constraintEnd_toStartOf="@+id/select_mode"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/entries_bar" /> app:layout_constraintTop_toBottomOf="@+id/entries_bar" />
<ImageButton
android:id="@+id/select_mode"
android:layout_width="64dp"
android:layout_height="48dp"
android:backgroundTint="@color/greySurface"
android:contentDescription="@string/placeholder"
android:src="@drawable/ic_satellite"
app:layout_constraintBottom_toBottomOf="@+id/select_all"
app:layout_constraintEnd_toStartOf="@+id/select_all"
app:layout_constraintTop_toTopOf="@+id/select_all" />
<ImageButton <ImageButton
android:id="@+id/select_all" android:id="@+id/select_all"
android:layout_width="64dp" android:layout_width="64dp"
+1
View File
@@ -79,4 +79,5 @@
к ним по лицензии <a href="https://www.apache.org/licenses/LICENSE-2.0">Apache-2.0</a></string> к ним по лицензии <a href="https://www.apache.org/licenses/LICENSE-2.0">Apache-2.0</a></string>
<string name="info_warranty"><a href="https://sites.google.com/view/look4sat-privacy-policy/home">Политика конфиденциальности</a> <string name="info_warranty"><a href="https://sites.google.com/view/look4sat-privacy-policy/home">Политика конфиденциальности</a>
\nЭто ПО поставляется без гарантий. \nПодробности в лицензии <a href="https://www.gnu.org/licenses/gpl-3.0.html">GNU GPLv3</a>.</string> \nЭто ПО поставляется без гарантий. \nПодробности в лицензии <a href="https://www.gnu.org/licenses/gpl-3.0.html">GNU GPLv3</a>.</string>
<string name="modes_title">Выберите модуляцию трансивера</string>
</resources> </resources>
+1
View File
@@ -99,4 +99,5 @@
</string> </string>
<string name="info_warranty"><a href="https://sites.google.com/view/look4sat-privacy-policy/home">Privacy Policy</a> <string name="info_warranty"><a href="https://sites.google.com/view/look4sat-privacy-policy/home">Privacy Policy</a>
\nThis app comes with no warranty. \nSee the <a href="https://www.gnu.org/licenses/gpl-3.0.html">GNU GPLv3</a> license for details.</string> \nThis app comes with no warranty. \nSee the <a href="https://www.gnu.org/licenses/gpl-3.0.html">GNU GPLv3</a> license for details.</string>
<string name="modes_title">Choose transmitter mode</string>
</resources> </resources>