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 // 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") @Query("SELECT * FROM transmitters WHERE catNum = :catNum")
fun getTransmittersByCatNum(catNum: Int): Flow<List<SatTrans>> 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") @Query("SELECT tle FROM entries WHERE isSelected = 1")
suspend fun getSelectedSatellites(): List<Satellite> suspend fun getSelectedSatellites(): List<Satellite>
@@ -59,13 +59,20 @@ interface SatelliteDao {
// Update // Update
@Transaction @Transaction
suspend fun updateEntriesSelection(catNums: List<Int>, isSelected: Boolean) { suspend fun restoreEntriesSelection(catNums: List<Int>, isSelected: Boolean) {
clearEntriesSelection() clearEntriesSelection()
catNums.forEach { catNum -> catNums.forEach { catNum ->
updateItemSelection(catNum, isSelected) updateItemSelection(catNum, isSelected)
} }
} }
@Transaction
suspend fun updateEntriesSelection(catNums: List<Int>, isSelected: Boolean) {
catNums.forEach { catNum ->
updateItemSelection(catNum, isSelected)
}
}
@Query("UPDATE entries SET isSelected = 0") @Query("UPDATE entries SET isSelected = 0")
suspend fun clearEntriesSelection() suspend fun clearEntriesSelection()
@@ -74,15 +81,6 @@ interface SatelliteDao {
// Delete // Delete
@Transaction
suspend fun deleteAllData() {
deleteEntries()
deleteTransmitters()
}
@Query("DELETE from entries")
suspend fun deleteEntries()
@Query("DELETE from transmitters") @Query("DELETE from transmitters")
suspend fun deleteTransmitters() suspend fun deleteTransmitters()
} }
@@ -48,14 +48,14 @@ class SatelliteRepo @Inject constructor(
// Query // Query
fun getAllSatItems(): Flow<List<SatItem>> {
return satelliteDao.getAllSatItems()
}
fun getSatTransmitters(catNum: Int): Flow<List<SatTrans>> { fun getSatTransmitters(catNum: Int): Flow<List<SatTrans>> {
return satelliteDao.getTransmittersByCatNum(catNum) return satelliteDao.getTransmittersByCatNum(catNum)
} }
suspend fun getAllSatItems(): List<SatItem> {
return satelliteDao.getAllSatItems()
}
suspend fun getSelectedSatellites(): List<Satellite> { suspend fun getSelectedSatellites(): List<Satellite> {
return satelliteDao.getSelectedSatellites() return satelliteDao.getSelectedSatellites()
} }
@@ -131,7 +131,7 @@ class SatelliteRepo @Inject constructor(
private suspend fun insertEntriesAndRestoreSelection(entries: List<SatEntry>) { private suspend fun insertEntriesAndRestoreSelection(entries: List<SatEntry>) {
val selectedCatNums = satelliteDao.getSelectedCatNums() val selectedCatNums = satelliteDao.getSelectedCatNums()
satelliteDao.insertEntries(entries) satelliteDao.insertEntries(entries)
satelliteDao.updateEntriesSelection(selectedCatNums, true) satelliteDao.restoreEntriesSelection(selectedCatNums, true)
} }
// Update // Update
@@ -40,9 +40,14 @@ class EntriesAdapter : RecyclerView.Adapter<EntriesAdapter.SatItemHolder>() {
private val listDiffer = AsyncListDiffer(this, diffCallback) private val listDiffer = AsyncListDiffer(this, diffCallback)
private val allItems = mutableListOf<SatItem>() private val allItems = mutableListOf<SatItem>()
private var shouldSelectAll = true private var shouldSelectAll = true
private lateinit var entriesClickListener: EntriesClickListener
fun getSelectedIds(): List<Int> { interface EntriesClickListener {
return allItems.filter { it.isSelected }.map { it.catNum } fun updateSelection(catNums: List<Int>, isSelected: Boolean)
}
fun setEntriesClickListener(listener: EntriesClickListener) {
entriesClickListener = listener
} }
fun submitAllItems(items: List<SatItem>) { fun submitAllItems(items: List<SatItem>) {
@@ -58,6 +63,7 @@ class EntriesAdapter : RecyclerView.Adapter<EntriesAdapter.SatItemHolder>() {
newList.add(item) newList.add(item)
} }
submitCurrentItems(newList) submitCurrentItems(newList)
entriesClickListener.updateSelection(newList.map { it.catNum }, shouldSelectAll)
shouldSelectAll = !shouldSelectAll shouldSelectAll = !shouldSelectAll
} }
@@ -98,16 +104,19 @@ class EntriesAdapter : RecyclerView.Adapter<EntriesAdapter.SatItemHolder>() {
} }
override fun onBindViewHolder(holder: SatItemHolder, position: Int) { 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) : class SatItemHolder private constructor(private val binding: ItemSatEntryBinding) :
RecyclerView.ViewHolder(binding.root) { RecyclerView.ViewHolder(binding.root) {
fun bind(item: SatItem) { fun bind(item: SatItem, listener: EntriesClickListener) {
binding.satItemCheckbox.text = item.name binding.satItemCheckbox.text = item.name
binding.satItemCheckbox.isChecked = item.isSelected 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 { companion object {
@@ -23,11 +23,9 @@ import android.widget.SearchView
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
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
@@ -37,7 +35,6 @@ import com.rtbishop.look4sat.databinding.FragmentEntriesBinding
import com.rtbishop.look4sat.utility.RecyclerDivider import com.rtbishop.look4sat.utility.RecyclerDivider
import com.rtbishop.look4sat.utility.getNavResult import com.rtbishop.look4sat.utility.getNavResult
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
@AndroidEntryPoint @AndroidEntryPoint
class EntriesFragment : Fragment(R.layout.fragment_entries), SearchView.OnQueryTextListener { 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) { private fun setupComponents(view: View) {
entriesAdapter = EntriesAdapter() entriesAdapter = EntriesAdapter().apply {
setEntriesClickListener(viewModel)
}
binding = FragmentEntriesBinding.bind(view).apply { binding = FragmentEntriesBinding.bind(view).apply {
entriesRecycler.apply { entriesRecycler.apply {
setHasFixedSize(true) setHasFixedSize(true)
@@ -70,7 +69,6 @@ class EntriesFragment : Fragment(R.layout.fragment_entries), SearchView.OnQueryT
importFile.setOnClickListener { filePicker.launch("*/*") } importFile.setOnClickListener { filePicker.launch("*/*") }
selectMode.setOnClickListener { showModesDialog() } selectMode.setOnClickListener { showModesDialog() }
selectAll.setOnClickListener { entriesAdapter?.selectCurrentItems() } selectAll.setOnClickListener { entriesAdapter?.selectCurrentItems() }
entriesSubmit.setOnClickListener { updateEntriesSelection() }
searchBar.setOnQueryTextListener(this@EntriesFragment) searchBar.setOnQueryTextListener(this@EntriesFragment)
} }
} }
@@ -97,14 +95,14 @@ class EntriesFragment : Fragment(R.layout.fragment_entries), SearchView.OnQueryT
private fun setLoaded(items: List<SatItem>) { private fun setLoaded(items: List<SatItem>) {
if (items.isEmpty()) { if (items.isEmpty()) {
binding?.apply { binding?.apply {
entriesError.visibility = View.VISIBLE entriesEmptyError.visibility = View.VISIBLE
entriesProgress.visibility = View.INVISIBLE entriesProgress.visibility = View.INVISIBLE
entriesRecycler.visibility = View.INVISIBLE entriesRecycler.visibility = View.INVISIBLE
} }
} else { } else {
entriesAdapter?.submitAllItems(items) entriesAdapter?.submitAllItems(items)
binding?.apply { binding?.apply {
entriesError.visibility = View.INVISIBLE entriesEmptyError.visibility = View.INVISIBLE
entriesProgress.visibility = View.INVISIBLE entriesProgress.visibility = View.INVISIBLE
entriesRecycler.visibility = View.VISIBLE entriesRecycler.visibility = View.VISIBLE
entriesRecycler.scrollToPosition(0) entriesRecycler.scrollToPosition(0)
@@ -114,7 +112,7 @@ class EntriesFragment : Fragment(R.layout.fragment_entries), SearchView.OnQueryT
private fun setLoading() { private fun setLoading() {
binding?.apply { binding?.apply {
entriesError.visibility = View.INVISIBLE entriesEmptyError.visibility = View.INVISIBLE
entriesProgress.visibility = View.VISIBLE entriesProgress.visibility = View.VISIBLE
entriesRecycler.visibility = View.INVISIBLE entriesRecycler.visibility = View.INVISIBLE
} }
@@ -122,7 +120,7 @@ class EntriesFragment : Fragment(R.layout.fragment_entries), SearchView.OnQueryT
private fun setError() { private fun setError() {
binding?.apply { binding?.apply {
entriesError.visibility = View.INVISIBLE entriesEmptyError.visibility = View.INVISIBLE
entriesProgress.visibility = View.INVISIBLE entriesProgress.visibility = View.INVISIBLE
entriesRecycler.visibility = View.VISIBLE 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() 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() { private fun showModesDialog() {
val modes = arrayOf( viewModel.createModesDialog(requireContext()).show()
"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()
}
} }
override fun onQueryTextSubmit(query: String): Boolean { 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 { override fun onQueryTextChange(newText: String): Boolean {
filterAndScroll(newText) entriesAdapter?.filterItems(newText)
binding?.entriesRecycler?.scrollToPosition(0)
return true return true
} }
@@ -17,10 +17,14 @@
*/ */
package com.rtbishop.look4sat.ui.entriesScreen package com.rtbishop.look4sat.ui.entriesScreen
import android.content.Context
import android.net.Uri import android.net.Uri
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope 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.Result
import com.rtbishop.look4sat.data.model.SatItem import com.rtbishop.look4sat.data.model.SatItem
import com.rtbishop.look4sat.data.model.TleSource 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 com.rtbishop.look4sat.data.repository.SatelliteRepo
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@@ -38,27 +41,24 @@ import kotlin.system.measureTimeMillis
class EntriesViewModel @Inject constructor( class EntriesViewModel @Inject constructor(
private val prefsRepo: PrefsRepo, private val prefsRepo: PrefsRepo,
private val satelliteRepo: SatelliteRepo 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) private val satDataState = MutableStateFlow<Result<List<SatItem>>>(Result.InProgress)
val satData = satDataState.asLiveData(viewModelScope.coroutineContext) val satData = satDataState.asLiveData(viewModelScope.coroutineContext)
init { init {
loadAndFilterData()
}
private fun loadAndFilterData() {
viewModelScope.launch { viewModelScope.launch {
satelliteRepo.getAllSatItems().collect { items -> allSatItems = satelliteRepo.getAllSatItems()
allSatItems.clear() filterByModes(prefsRepo.loadModesSelection())
allSatItems.addAll(items)
filterByModes(getModesSelection())
}
} }
} }
fun getModesSelection(): List<String> { private fun filterByModes(modes: List<String>) {
return prefsRepo.loadModesSelection()
}
fun filterByModes(modes: List<String>) {
if (modes.isEmpty()) { if (modes.isEmpty()) {
satDataState.value = Result.Success(allSatItems) satDataState.value = Result.Success(allSatItems)
} else { } else {
@@ -75,6 +75,7 @@ class EntriesViewModel @Inject constructor(
satDataState.value = Result.InProgress satDataState.value = Result.InProgress
try { try {
satelliteRepo.importSatDataFromFile(uri) satelliteRepo.importSatDataFromFile(uri)
loadAndFilterData()
} catch (exception: Exception) { } catch (exception: Exception) {
satDataState.value = Result.Error(exception) satDataState.value = Result.Error(exception)
} }
@@ -88,6 +89,7 @@ class EntriesViewModel @Inject constructor(
try { try {
prefsRepo.saveTleSources(sources) prefsRepo.saveTleSources(sources)
satelliteRepo.importSatDataFromWeb(sources) satelliteRepo.importSatDataFromWeb(sources)
loadAndFilterData()
} catch (exception: Exception) { } catch (exception: Exception) {
satDataState.value = Result.Error(exception) satDataState.value = Result.Error(exception)
} }
@@ -96,7 +98,38 @@ class EntriesViewModel @Inject constructor(
} }
} }
suspend fun updateEntriesSelection(items: List<Int>, isSelected: Boolean) { fun createModesDialog(context: Context): AlertDialog {
satelliteRepo.updateEntriesSelection(items, isSelected) 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" /> app:layout_constraintTop_toBottomOf="@+id/entries_bar" />
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/entries_error" android:id="@+id/entries_empty_error"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/entries_no_data" android:text="@string/entries_no_data"
@@ -116,14 +116,5 @@
</androidx.constraintlayout.widget.ConstraintLayout> </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> </androidx.coordinatorlayout.widget.CoordinatorLayout>