Further modification of entries selection logic

This commit is contained in:
Arty Bishop committed 2021-03-28 11:02:08 +01:00
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)
}
}
}
+1 -10
View File
@@ -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>