mirror of
https://github.com/rt-bishop/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
Further modification of entries selection logic
This commit is contained in:
1 parent
040ad844f5
commit
2ba06c779f
6 files changed
+90
-101
No files matched your search
@@ -29,13 +29,13 @@ interface SatelliteDao {
|
||||
|
||||
// Query
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT catNum, name, isSelected FROM entries ORDER BY name ASC")
|
||||
fun getAllSatItems(): Flow<List<SatItem>>
|
||||
|
||||
@Query("SELECT * FROM transmitters WHERE catNum = :catNum")
|
||||
fun getTransmittersByCatNum(catNum: Int): Flow<List<SatTrans>>
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT catNum, name, isSelected FROM entries ORDER BY name ASC")
|
||||
suspend fun getAllSatItems(): List<SatItem>
|
||||
|
||||
@Query("SELECT tle FROM entries WHERE isSelected = 1")
|
||||
suspend fun getSelectedSatellites(): List<Satellite>
|
||||
|
||||
@@ -59,13 +59,20 @@ interface SatelliteDao {
|
||||
// Update
|
||||
|
||||
@Transaction
|
||||
suspend fun updateEntriesSelection(catNums: List<Int>, isSelected: Boolean) {
|
||||
suspend fun restoreEntriesSelection(catNums: List<Int>, isSelected: Boolean) {
|
||||
clearEntriesSelection()
|
||||
catNums.forEach { catNum ->
|
||||
updateItemSelection(catNum, isSelected)
|
||||
}
|
||||
}
|
||||
|
||||
@Transaction
|
||||
suspend fun updateEntriesSelection(catNums: List<Int>, isSelected: Boolean) {
|
||||
catNums.forEach { catNum ->
|
||||
updateItemSelection(catNum, isSelected)
|
||||
}
|
||||
}
|
||||
|
||||
@Query("UPDATE entries SET isSelected = 0")
|
||||
suspend fun clearEntriesSelection()
|
||||
|
||||
@@ -74,15 +81,6 @@ interface SatelliteDao {
|
||||
|
||||
// Delete
|
||||
|
||||
@Transaction
|
||||
suspend fun deleteAllData() {
|
||||
deleteEntries()
|
||||
deleteTransmitters()
|
||||
}
|
||||
|
||||
@Query("DELETE from entries")
|
||||
suspend fun deleteEntries()
|
||||
|
||||
@Query("DELETE from transmitters")
|
||||
suspend fun deleteTransmitters()
|
||||
}
|
||||
@@ -48,14 +48,14 @@ class SatelliteRepo @Inject constructor(
|
||||
|
||||
// Query
|
||||
|
||||
fun getAllSatItems(): Flow<List<SatItem>> {
|
||||
return satelliteDao.getAllSatItems()
|
||||
}
|
||||
|
||||
fun getSatTransmitters(catNum: Int): Flow<List<SatTrans>> {
|
||||
return satelliteDao.getTransmittersByCatNum(catNum)
|
||||
}
|
||||
|
||||
suspend fun getAllSatItems(): List<SatItem> {
|
||||
return satelliteDao.getAllSatItems()
|
||||
}
|
||||
|
||||
suspend fun getSelectedSatellites(): List<Satellite> {
|
||||
return satelliteDao.getSelectedSatellites()
|
||||
}
|
||||
@@ -131,7 +131,7 @@ class SatelliteRepo @Inject constructor(
|
||||
private suspend fun insertEntriesAndRestoreSelection(entries: List<SatEntry>) {
|
||||
val selectedCatNums = satelliteDao.getSelectedCatNums()
|
||||
satelliteDao.insertEntries(entries)
|
||||
satelliteDao.updateEntriesSelection(selectedCatNums, true)
|
||||
satelliteDao.restoreEntriesSelection(selectedCatNums, true)
|
||||
}
|
||||
|
||||
// Update
|
||||
|
||||
@@ -40,9 +40,14 @@ class EntriesAdapter : RecyclerView.Adapter<EntriesAdapter.SatItemHolder>() {
|
||||
private val listDiffer = AsyncListDiffer(this, diffCallback)
|
||||
private val allItems = mutableListOf<SatItem>()
|
||||
private var shouldSelectAll = true
|
||||
private lateinit var entriesClickListener: EntriesClickListener
|
||||
|
||||
fun getSelectedIds(): List<Int> {
|
||||
return allItems.filter { it.isSelected }.map { it.catNum }
|
||||
interface EntriesClickListener {
|
||||
fun updateSelection(catNums: List<Int>, isSelected: Boolean)
|
||||
}
|
||||
|
||||
fun setEntriesClickListener(listener: EntriesClickListener) {
|
||||
entriesClickListener = listener
|
||||
}
|
||||
|
||||
fun submitAllItems(items: List<SatItem>) {
|
||||
@@ -58,6 +63,7 @@ class EntriesAdapter : RecyclerView.Adapter<EntriesAdapter.SatItemHolder>() {
|
||||
newList.add(item)
|
||||
}
|
||||
submitCurrentItems(newList)
|
||||
entriesClickListener.updateSelection(newList.map { it.catNum }, shouldSelectAll)
|
||||
shouldSelectAll = !shouldSelectAll
|
||||
}
|
||||
|
||||
@@ -98,16 +104,19 @@ class EntriesAdapter : RecyclerView.Adapter<EntriesAdapter.SatItemHolder>() {
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: SatItemHolder, position: Int) {
|
||||
holder.bind(listDiffer.currentList[position])
|
||||
holder.bind(listDiffer.currentList[position], entriesClickListener)
|
||||
}
|
||||
|
||||
class SatItemHolder private constructor(private val binding: ItemSatEntryBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(item: SatItem) {
|
||||
fun bind(item: SatItem, listener: EntriesClickListener) {
|
||||
binding.satItemCheckbox.text = item.name
|
||||
binding.satItemCheckbox.isChecked = item.isSelected
|
||||
itemView.setOnClickListener { item.isSelected = item.isSelected.not() }
|
||||
itemView.setOnClickListener {
|
||||
item.isSelected = item.isSelected.not()
|
||||
listener.updateSelection(listOf(item.catNum), item.isSelected.not())
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -23,11 +23,9 @@ import android.widget.SearchView
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.rtbishop.look4sat.R
|
||||
import com.rtbishop.look4sat.data.model.Result
|
||||
@@ -37,7 +35,6 @@ import com.rtbishop.look4sat.databinding.FragmentEntriesBinding
|
||||
import com.rtbishop.look4sat.utility.RecyclerDivider
|
||||
import com.rtbishop.look4sat.utility.getNavResult
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@AndroidEntryPoint
|
||||
class EntriesFragment : Fragment(R.layout.fragment_entries), SearchView.OnQueryTextListener {
|
||||
@@ -57,7 +54,9 @@ class EntriesFragment : Fragment(R.layout.fragment_entries), SearchView.OnQueryT
|
||||
}
|
||||
|
||||
private fun setupComponents(view: View) {
|
||||
entriesAdapter = EntriesAdapter()
|
||||
entriesAdapter = EntriesAdapter().apply {
|
||||
setEntriesClickListener(viewModel)
|
||||
}
|
||||
binding = FragmentEntriesBinding.bind(view).apply {
|
||||
entriesRecycler.apply {
|
||||
setHasFixedSize(true)
|
||||
@@ -70,7 +69,6 @@ class EntriesFragment : Fragment(R.layout.fragment_entries), SearchView.OnQueryT
|
||||
importFile.setOnClickListener { filePicker.launch("*/*") }
|
||||
selectMode.setOnClickListener { showModesDialog() }
|
||||
selectAll.setOnClickListener { entriesAdapter?.selectCurrentItems() }
|
||||
entriesSubmit.setOnClickListener { updateEntriesSelection() }
|
||||
searchBar.setOnQueryTextListener(this@EntriesFragment)
|
||||
}
|
||||
}
|
||||
@@ -97,14 +95,14 @@ class EntriesFragment : Fragment(R.layout.fragment_entries), SearchView.OnQueryT
|
||||
private fun setLoaded(items: List<SatItem>) {
|
||||
if (items.isEmpty()) {
|
||||
binding?.apply {
|
||||
entriesError.visibility = View.VISIBLE
|
||||
entriesEmptyError.visibility = View.VISIBLE
|
||||
entriesProgress.visibility = View.INVISIBLE
|
||||
entriesRecycler.visibility = View.INVISIBLE
|
||||
}
|
||||
} else {
|
||||
entriesAdapter?.submitAllItems(items)
|
||||
binding?.apply {
|
||||
entriesError.visibility = View.INVISIBLE
|
||||
entriesEmptyError.visibility = View.INVISIBLE
|
||||
entriesProgress.visibility = View.INVISIBLE
|
||||
entriesRecycler.visibility = View.VISIBLE
|
||||
entriesRecycler.scrollToPosition(0)
|
||||
@@ -114,7 +112,7 @@ class EntriesFragment : Fragment(R.layout.fragment_entries), SearchView.OnQueryT
|
||||
|
||||
private fun setLoading() {
|
||||
binding?.apply {
|
||||
entriesError.visibility = View.INVISIBLE
|
||||
entriesEmptyError.visibility = View.INVISIBLE
|
||||
entriesProgress.visibility = View.VISIBLE
|
||||
entriesRecycler.visibility = View.INVISIBLE
|
||||
}
|
||||
@@ -122,7 +120,7 @@ class EntriesFragment : Fragment(R.layout.fragment_entries), SearchView.OnQueryT
|
||||
|
||||
private fun setError() {
|
||||
binding?.apply {
|
||||
entriesError.visibility = View.INVISIBLE
|
||||
entriesEmptyError.visibility = View.INVISIBLE
|
||||
entriesProgress.visibility = View.INVISIBLE
|
||||
entriesRecycler.visibility = View.VISIBLE
|
||||
}
|
||||
@@ -130,49 +128,8 @@ class EntriesFragment : Fragment(R.layout.fragment_entries), SearchView.OnQueryT
|
||||
Snackbar.make(requireView(), errorMsg, Snackbar.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
private fun filterAndScroll(query: String) {
|
||||
entriesAdapter?.filterItems(query)
|
||||
binding?.entriesRecycler?.scrollToPosition(0)
|
||||
}
|
||||
|
||||
private fun updateEntriesSelection() {
|
||||
setLoading()
|
||||
lifecycleScope.launch {
|
||||
entriesAdapter?.getSelectedIds()?.let { ids ->
|
||||
viewModel.updateEntriesSelection(ids, true)
|
||||
}
|
||||
findNavController().navigate(R.id.nav_passes)
|
||||
}
|
||||
}
|
||||
|
||||
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 ->
|
||||
when {
|
||||
isChecked -> selectedModes.add(modes[which])
|
||||
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()
|
||||
}
|
||||
viewModel.createModesDialog(requireContext()).show()
|
||||
}
|
||||
|
||||
override fun onQueryTextSubmit(query: String): Boolean {
|
||||
@@ -180,7 +137,8 @@ class EntriesFragment : Fragment(R.layout.fragment_entries), SearchView.OnQueryT
|
||||
}
|
||||
|
||||
override fun onQueryTextChange(newText: String): Boolean {
|
||||
filterAndScroll(newText)
|
||||
entriesAdapter?.filterItems(newText)
|
||||
binding?.entriesRecycler?.scrollToPosition(0)
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -17,10 +17,14 @@
|
||||
*/
|
||||
package com.rtbishop.look4sat.ui.entriesScreen
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.rtbishop.look4sat.R
|
||||
import com.rtbishop.look4sat.data.model.Result
|
||||
import com.rtbishop.look4sat.data.model.SatItem
|
||||
import com.rtbishop.look4sat.data.model.TleSource
|
||||
@@ -28,7 +32,6 @@ import com.rtbishop.look4sat.data.repository.PrefsRepo
|
||||
import com.rtbishop.look4sat.data.repository.SatelliteRepo
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
@@ -38,27 +41,24 @@ import kotlin.system.measureTimeMillis
|
||||
class EntriesViewModel @Inject constructor(
|
||||
private val prefsRepo: PrefsRepo,
|
||||
private val satelliteRepo: SatelliteRepo
|
||||
) : ViewModel() {
|
||||
) : ViewModel(), EntriesAdapter.EntriesClickListener {
|
||||
|
||||
private val allSatItems = mutableListOf<SatItem>()
|
||||
private var allSatItems = listOf<SatItem>()
|
||||
private val satDataState = MutableStateFlow<Result<List<SatItem>>>(Result.InProgress)
|
||||
val satData = satDataState.asLiveData(viewModelScope.coroutineContext)
|
||||
|
||||
init {
|
||||
loadAndFilterData()
|
||||
}
|
||||
|
||||
private fun loadAndFilterData() {
|
||||
viewModelScope.launch {
|
||||
satelliteRepo.getAllSatItems().collect { items ->
|
||||
allSatItems.clear()
|
||||
allSatItems.addAll(items)
|
||||
filterByModes(getModesSelection())
|
||||
}
|
||||
allSatItems = satelliteRepo.getAllSatItems()
|
||||
filterByModes(prefsRepo.loadModesSelection())
|
||||
}
|
||||
}
|
||||
|
||||
fun getModesSelection(): List<String> {
|
||||
return prefsRepo.loadModesSelection()
|
||||
}
|
||||
|
||||
fun filterByModes(modes: List<String>) {
|
||||
private fun filterByModes(modes: List<String>) {
|
||||
if (modes.isEmpty()) {
|
||||
satDataState.value = Result.Success(allSatItems)
|
||||
} else {
|
||||
@@ -75,6 +75,7 @@ class EntriesViewModel @Inject constructor(
|
||||
satDataState.value = Result.InProgress
|
||||
try {
|
||||
satelliteRepo.importSatDataFromFile(uri)
|
||||
loadAndFilterData()
|
||||
} catch (exception: Exception) {
|
||||
satDataState.value = Result.Error(exception)
|
||||
}
|
||||
@@ -88,6 +89,7 @@ class EntriesViewModel @Inject constructor(
|
||||
try {
|
||||
prefsRepo.saveTleSources(sources)
|
||||
satelliteRepo.importSatDataFromWeb(sources)
|
||||
loadAndFilterData()
|
||||
} catch (exception: Exception) {
|
||||
satDataState.value = Result.Error(exception)
|
||||
}
|
||||
@@ -96,7 +98,38 @@ class EntriesViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateEntriesSelection(items: List<Int>, isSelected: Boolean) {
|
||||
satelliteRepo.updateEntriesSelection(items, isSelected)
|
||||
fun createModesDialog(context: Context): AlertDialog {
|
||||
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 = prefsRepo.loadModesSelection().toMutableList()
|
||||
selectedModes.forEach { savedModes[modes.indexOf(it)] = true }
|
||||
val dialogBuilder = MaterialAlertDialogBuilder(context).apply {
|
||||
setTitle(context.getString(R.string.modes_title))
|
||||
setMultiChoiceItems(modes, savedModes) { _, which, isChecked ->
|
||||
when {
|
||||
isChecked -> selectedModes.add(modes[which])
|
||||
selectedModes.contains(modes[which]) -> selectedModes.remove(modes[which])
|
||||
}
|
||||
}
|
||||
setPositiveButton(context.getString(android.R.string.ok)) { _, _ ->
|
||||
filterByModes(selectedModes)
|
||||
}
|
||||
setNeutralButton(context.getString(android.R.string.cancel)) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
}
|
||||
return dialogBuilder.create()
|
||||
}
|
||||
|
||||
override fun updateSelection(catNums: List<Int>, isSelected: Boolean) {
|
||||
viewModelScope.launch {
|
||||
satelliteRepo.updateEntriesSelection(catNums, isSelected)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,7 +103,7 @@
|
||||
app:layout_constraintTop_toBottomOf="@+id/entries_bar" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/entries_error"
|
||||
android:id="@+id/entries_empty_error"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/entries_no_data"
|
||||
@@ -116,14 +116,5 @@
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/entries_submit"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|center"
|
||||
android:layout_margin="@dimen/fab_padding"
|
||||
android:contentDescription="@string/placeholder"
|
||||
android:src="@drawable/ic_arrow" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
Reference in new issue
Block a user