Further refactoring of entries selection and filtering logic

This commit is contained in:
Arty Bishop committed 2021-03-27 18:57:08 +00:00
1 parent 8d8e0bb7ac
commit 76dce66a4a
6 files changed
+96 -113

No files matched your search

@@ -33,9 +33,6 @@ interface SatelliteDao {
@Query("SELECT catNum, name, isSelected FROM entries ORDER BY name ASC")
fun getAllSatItems(): Flow<List<SatItem>>
@Query("SELECT * FROM transmitters WHERE isAlive = 1 ORDER BY mode ASC")
fun getAllTransmitters(): Flow<List<SatTrans>>
@Query("SELECT * FROM transmitters WHERE isAlive = 1 and catNum = :catNum")
fun getTransmittersByCatNum(catNum: Int): Flow<List<SatTrans>>
@@ -55,16 +52,9 @@ interface SatelliteDao {
// Update
@Transaction
suspend fun restoreEntriesSelection(catNums: List<Int>, isSelected: Boolean) {
clearEntriesSelection()
catNums.forEach { catNum ->
updateItemSelection(catNum, isSelected)
}
}
@Transaction
suspend fun updateEntriesSelection(catNums: List<Int>, isSelected: Boolean) {
clearEntriesSelection()
catNums.forEach { catNum ->
updateItemSelection(catNum, isSelected)
}
@@ -52,10 +52,6 @@ class SatelliteRepo @Inject constructor(
return satelliteDao.getAllSatItems()
}
fun getAllTransmitters(): Flow<List<SatTrans>> {
return satelliteDao.getAllTransmitters()
}
fun getSatTransmitters(catNum: Int): Flow<List<SatTrans>> {
return satelliteDao.getTransmittersByCatNum(catNum)
}
@@ -135,7 +131,7 @@ class SatelliteRepo @Inject constructor(
private suspend fun insertEntriesAndRestoreSelection(entries: List<SatEntry>) {
val selectedCatNums = satelliteDao.getSelectedCatNums()
satelliteDao.insertEntries(entries)
satelliteDao.restoreEntriesSelection(selectedCatNums, true)
satelliteDao.updateEntriesSelection(selectedCatNums, true)
}
// Update
@@ -143,10 +139,4 @@ class SatelliteRepo @Inject constructor(
suspend fun updateEntriesSelection(catNums: List<Int>, isSelected: Boolean) {
satelliteDao.updateEntriesSelection(catNums, isSelected)
}
// Delete
suspend fun deleteAllData() {
satelliteDao.deleteAllData()
}
}
@@ -34,30 +34,30 @@ class EntriesAdapter : RecyclerView.Adapter<EntriesAdapter.SatItemHolder>() {
}
override fun areContentsTheSame(oldItem: SatItem, newItem: SatItem): Boolean {
return oldItem.isSelected == newItem.isSelected
return oldItem.isSelected != newItem.isSelected
}
}
private val listDiffer = AsyncListDiffer(this, diffCallback)
private var allItems = emptyList<SatItem>()
private val allItems = mutableListOf<SatItem>()
private var shouldSelectAll = true
private lateinit var entriesClickListener: EntriesClickListener
interface EntriesClickListener {
fun updateSelection(catNums: List<Int>, isSelected: Boolean)
}
fun setEntriesClickListener(listener: EntriesClickListener) {
entriesClickListener = listener
fun getSelectedIds(): List<Int> {
return allItems.filter { it.isSelected }.map { it.catNum }
}
fun submitAllItems(items: List<SatItem>) {
allItems = items
allItems.clear()
allItems.addAll(items)
listDiffer.submitList(items)
}
fun selectAllItems() {
val catNums = listDiffer.currentList.map { it.catNum }
entriesClickListener.updateSelection(catNums, shouldSelectAll)
fun selectCurrentItems() {
val newList = mutableListOf<SatItem>()
listDiffer.currentList.forEach { item ->
item.isSelected = shouldSelectAll
newList.add(item)
}
submitCurrentItems(newList)
shouldSelectAll = !shouldSelectAll
}
@@ -82,11 +82,11 @@ class EntriesAdapter : RecyclerView.Adapter<EntriesAdapter.SatItemHolder>() {
submitCurrentItems(allItems.filter { it.catNum == catNum })
}
private fun filterByName(query: String) {
val searchQuery = query.toLowerCase(Locale.getDefault())
val filteredItems = allItems.filter { satItem ->
val lowerCaseItem = satItem.name.toLowerCase(Locale.getDefault())
lowerCaseItem.contains(searchQuery)
private fun filterByName(name: String) {
val satName = name.toLowerCase(Locale.getDefault())
val filteredItems = allItems.filter { item ->
val itemName = item.name.toLowerCase(Locale.getDefault())
itemName.contains(satName)
}
submitCurrentItems(filteredItems)
}
@@ -98,18 +98,16 @@ class EntriesAdapter : RecyclerView.Adapter<EntriesAdapter.SatItemHolder>() {
}
override fun onBindViewHolder(holder: SatItemHolder, position: Int) {
holder.bind(listDiffer.currentList[position], entriesClickListener)
holder.bind(listDiffer.currentList[position])
}
class SatItemHolder private constructor(private val binding: ItemSatEntryBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: SatItem, clickListener: EntriesClickListener) {
fun bind(item: SatItem) {
binding.satItemCheckbox.text = item.name
binding.satItemCheckbox.isChecked = item.isSelected
itemView.setOnClickListener {
clickListener.updateSelection(listOf(item.catNum), item.isSelected.not())
}
itemView.setOnClickListener { item.isSelected = item.isSelected.not() }
}
companion object {
@@ -18,12 +18,12 @@
package com.rtbishop.look4sat.ui.entriesScreen
import android.os.Bundle
import android.text.Editable
import android.view.View
import android.widget.SearchView
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.widget.addTextChangedListener
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
@@ -31,16 +31,16 @@ 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
import com.rtbishop.look4sat.data.model.SatItem
import com.rtbishop.look4sat.data.model.TleSource
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.FlowPreview
import kotlinx.coroutines.launch
@FlowPreview
@AndroidEntryPoint
class EntriesFragment : Fragment(R.layout.fragment_entries), EntriesAdapter.EntriesClickListener {
class EntriesFragment : Fragment(R.layout.fragment_entries), SearchView.OnQueryTextListener {
private val viewModel: EntriesViewModel by viewModels()
private val filePicker =
@@ -52,17 +52,13 @@ class EntriesFragment : Fragment(R.layout.fragment_entries), EntriesAdapter.Entr
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = FragmentEntriesBinding.bind(view)
setupComponents()
observeSatelliteData()
observeSourcesResult()
setupComponents(view)
setupObservers()
}
private fun setupComponents() {
entriesAdapter = EntriesAdapter().apply {
setEntriesClickListener(this@EntriesFragment)
}
binding?.apply {
private fun setupComponents(view: View) {
entriesAdapter = EntriesAdapter()
binding = FragmentEntriesBinding.bind(view).apply {
entriesRecycler.apply {
setHasFixedSize(true)
adapter = entriesAdapter
@@ -70,39 +66,23 @@ class EntriesFragment : Fragment(R.layout.fragment_entries), EntriesAdapter.Entr
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
addItemDecoration(RecyclerDivider(R.drawable.rec_divider_light))
}
importWeb.setOnClickListener { showImportFromWebDialog() }
importWeb.setOnClickListener { findNavController().navigate(R.id.nav_dialog_sources) }
importFile.setOnClickListener { filePicker.launch("*/*") }
selectMode.setOnClickListener { showModesDialog() }
selectAll.setOnClickListener { entriesAdapter?.selectAllItems() }
searchBar.addTextChangedListener { query -> filterByQuery(query) }
searchBar.clearFocus()
selectAll.setOnClickListener { entriesAdapter?.selectCurrentItems() }
entriesSubmit.setOnClickListener { updateEntriesSelection() }
searchBar.setOnQueryTextListener(this@EntriesFragment)
}
}
private fun observeSatelliteData() {
private fun setupObservers() {
viewModel.satData.observe(viewLifecycleOwner, { result ->
when (result) {
is Result.Success -> {
if (result.data.isEmpty()) {
setEmpty()
} else {
entriesAdapter?.submitAllItems(result.data)
binding?.entriesRecycler?.smoothScrollToPosition(0)
setLoaded()
}
}
is Result.InProgress -> {
setLoading()
}
is Result.Error -> {
val errorMsg = getString(R.string.entries_update_error)
Snackbar.make(requireView(), errorMsg, Snackbar.LENGTH_SHORT).show()
}
is Result.Success -> setLoaded(result.data)
is Result.InProgress -> setLoading()
is Result.Error -> setError()
}
})
}
private fun observeSourcesResult() {
getNavResult<List<String>>(R.id.nav_entries, "sources") { result ->
result.map { TleSource(it) }.let { sources ->
if (sources.isNullOrEmpty()) {
@@ -114,11 +94,21 @@ class EntriesFragment : Fragment(R.layout.fragment_entries), EntriesAdapter.Entr
}
}
private fun setLoaded() {
binding?.apply {
entriesError.visibility = View.INVISIBLE
entriesProgress.visibility = View.INVISIBLE
entriesRecycler.visibility = View.VISIBLE
private fun setLoaded(items: List<SatItem>) {
if (items.isEmpty()) {
binding?.apply {
entriesError.visibility = View.VISIBLE
entriesProgress.visibility = View.INVISIBLE
entriesRecycler.visibility = View.INVISIBLE
}
} else {
entriesAdapter?.submitAllItems(items)
binding?.apply {
entriesError.visibility = View.INVISIBLE
entriesProgress.visibility = View.INVISIBLE
entriesRecycler.visibility = View.VISIBLE
entriesRecycler.scrollToPosition(0)
}
}
}
@@ -130,21 +120,29 @@ class EntriesFragment : Fragment(R.layout.fragment_entries), EntriesAdapter.Entr
}
}
private fun setEmpty() {
private fun setError() {
binding?.apply {
entriesError.visibility = View.VISIBLE
entriesError.visibility = View.INVISIBLE
entriesProgress.visibility = View.INVISIBLE
entriesRecycler.visibility = View.INVISIBLE
entriesRecycler.visibility = View.VISIBLE
}
val errorMsg = getString(R.string.entries_update_error)
Snackbar.make(requireView(), errorMsg, Snackbar.LENGTH_SHORT).show()
}
private fun filterByQuery(query: Editable?) {
entriesAdapter?.filterItems(query.toString())
binding?.entriesRecycler?.smoothScrollToPosition(0)
private fun filterAndScroll(query: String) {
entriesAdapter?.filterItems(query)
binding?.entriesRecycler?.scrollToPosition(0)
}
private fun showImportFromWebDialog() {
findNavController().navigate(R.id.nav_dialog_sources)
private fun updateEntriesSelection() {
setLoading()
lifecycleScope.launch {
entriesAdapter?.getSelectedIds()?.let { ids ->
viewModel.updateEntriesSelection(ids, true)
}
findNavController().navigate(R.id.nav_passes)
}
}
private fun showModesDialog() {
@@ -161,10 +159,9 @@ class EntriesFragment : Fragment(R.layout.fragment_entries), EntriesAdapter.Entr
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])
when {
isChecked -> selectedModes.add(modes[which])
selectedModes.contains(modes[which]) -> selectedModes.remove(modes[which])
}
}
setPositiveButton(getString(android.R.string.ok)) { _, _ ->
@@ -178,8 +175,13 @@ class EntriesFragment : Fragment(R.layout.fragment_entries), EntriesAdapter.Entr
}
}
override fun updateSelection(catNums: List<Int>, isSelected: Boolean) {
viewModel.updateEntriesSelection(catNums, isSelected)
override fun onQueryTextSubmit(query: String): Boolean {
return true
}
override fun onQueryTextChange(newText: String): Boolean {
filterAndScroll(newText)
return true
}
override fun onDestroyView() {
@@ -27,7 +27,6 @@ import com.rtbishop.look4sat.data.model.TleSource
import com.rtbishop.look4sat.data.repository.PrefsRepo
import com.rtbishop.look4sat.data.repository.SatelliteRepo
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
@@ -35,7 +34,6 @@ import timber.log.Timber
import javax.inject.Inject
import kotlin.system.measureTimeMillis
@FlowPreview
@HiltViewModel
class EntriesViewModel @Inject constructor(
private val prefsRepo: PrefsRepo,
@@ -98,9 +96,7 @@ class EntriesViewModel @Inject constructor(
}
}
fun updateEntriesSelection(items: List<Int>, isSelected: Boolean) {
viewModelScope.launch {
satelliteRepo.updateEntriesSelection(items, isSelected)
}
suspend fun updateEntriesSelection(items: List<Int>, isSelected: Boolean) {
satelliteRepo.updateEntriesSelection(items, isSelected)
}
}
+12 -5
View File
@@ -51,15 +51,13 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
<SearchView
android:id="@+id/search_bar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:hint="@string/entries_search_hint"
android:layout_marginStart="8dp"
android:iconifiedByDefault="false"
android:importantForAutofill="no"
android:inputType="text"
android:queryHint="@string/entries_search_hint"
app:layout_constraintEnd_toStartOf="@+id/select_mode"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/entries_bar" />
@@ -118,5 +116,14 @@
</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>