From 76dce66a4a91a804c1a4c62dc16719662a26b54c Mon Sep 17 00:00:00 2001 From: Arty Bishop Date: Sat, 27 Mar 2021 18:57:08 +0000 Subject: [PATCH] Further refactoring of entries selection and filtering logic --- .../look4sat/data/database/SatelliteDao.kt | 12 +- .../look4sat/data/repository/SatelliteRepo.kt | 12 +- .../ui/entriesScreen/EntriesAdapter.kt | 44 ++++--- .../ui/entriesScreen/EntriesFragment.kt | 116 +++++++++--------- .../ui/entriesScreen/EntriesViewModel.kt | 8 +- app/src/main/res/layout/fragment_entries.xml | 17 ++- 6 files changed, 96 insertions(+), 113 deletions(-) diff --git a/app/src/main/java/com/rtbishop/look4sat/data/database/SatelliteDao.kt b/app/src/main/java/com/rtbishop/look4sat/data/database/SatelliteDao.kt index e8b90027..5de036d6 100644 --- a/app/src/main/java/com/rtbishop/look4sat/data/database/SatelliteDao.kt +++ b/app/src/main/java/com/rtbishop/look4sat/data/database/SatelliteDao.kt @@ -33,9 +33,6 @@ interface SatelliteDao { @Query("SELECT catNum, name, isSelected FROM entries ORDER BY name ASC") fun getAllSatItems(): Flow> - @Query("SELECT * FROM transmitters WHERE isAlive = 1 ORDER BY mode ASC") - fun getAllTransmitters(): Flow> - @Query("SELECT * FROM transmitters WHERE isAlive = 1 and catNum = :catNum") fun getTransmittersByCatNum(catNum: Int): Flow> @@ -55,16 +52,9 @@ interface SatelliteDao { // Update - @Transaction - suspend fun restoreEntriesSelection(catNums: List, isSelected: Boolean) { - clearEntriesSelection() - catNums.forEach { catNum -> - updateItemSelection(catNum, isSelected) - } - } - @Transaction suspend fun updateEntriesSelection(catNums: List, isSelected: Boolean) { + clearEntriesSelection() catNums.forEach { catNum -> updateItemSelection(catNum, isSelected) } diff --git a/app/src/main/java/com/rtbishop/look4sat/data/repository/SatelliteRepo.kt b/app/src/main/java/com/rtbishop/look4sat/data/repository/SatelliteRepo.kt index 707c2d16..2be9c900 100644 --- a/app/src/main/java/com/rtbishop/look4sat/data/repository/SatelliteRepo.kt +++ b/app/src/main/java/com/rtbishop/look4sat/data/repository/SatelliteRepo.kt @@ -52,10 +52,6 @@ class SatelliteRepo @Inject constructor( return satelliteDao.getAllSatItems() } - fun getAllTransmitters(): Flow> { - return satelliteDao.getAllTransmitters() - } - fun getSatTransmitters(catNum: Int): Flow> { return satelliteDao.getTransmittersByCatNum(catNum) } @@ -135,7 +131,7 @@ class SatelliteRepo @Inject constructor( private suspend fun insertEntriesAndRestoreSelection(entries: List) { 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, isSelected: Boolean) { satelliteDao.updateEntriesSelection(catNums, isSelected) } - - // Delete - - suspend fun deleteAllData() { - satelliteDao.deleteAllData() - } } diff --git a/app/src/main/java/com/rtbishop/look4sat/ui/entriesScreen/EntriesAdapter.kt b/app/src/main/java/com/rtbishop/look4sat/ui/entriesScreen/EntriesAdapter.kt index 32ce1a12..6ff3ad14 100644 --- a/app/src/main/java/com/rtbishop/look4sat/ui/entriesScreen/EntriesAdapter.kt +++ b/app/src/main/java/com/rtbishop/look4sat/ui/entriesScreen/EntriesAdapter.kt @@ -34,30 +34,30 @@ class EntriesAdapter : RecyclerView.Adapter() { } 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() + private val allItems = mutableListOf() private var shouldSelectAll = true - private lateinit var entriesClickListener: EntriesClickListener - interface EntriesClickListener { - fun updateSelection(catNums: List, isSelected: Boolean) - } - - fun setEntriesClickListener(listener: EntriesClickListener) { - entriesClickListener = listener + fun getSelectedIds(): List { + return allItems.filter { it.isSelected }.map { it.catNum } } fun submitAllItems(items: List) { - 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() + listDiffer.currentList.forEach { item -> + item.isSelected = shouldSelectAll + newList.add(item) + } + submitCurrentItems(newList) shouldSelectAll = !shouldSelectAll } @@ -82,11 +82,11 @@ class EntriesAdapter : RecyclerView.Adapter() { 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() { } 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 { diff --git a/app/src/main/java/com/rtbishop/look4sat/ui/entriesScreen/EntriesFragment.kt b/app/src/main/java/com/rtbishop/look4sat/ui/entriesScreen/EntriesFragment.kt index feb4b51e..7311a038 100644 --- a/app/src/main/java/com/rtbishop/look4sat/ui/entriesScreen/EntriesFragment.kt +++ b/app/src/main/java/com/rtbishop/look4sat/ui/entriesScreen/EntriesFragment.kt @@ -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>(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) { + 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, 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() { diff --git a/app/src/main/java/com/rtbishop/look4sat/ui/entriesScreen/EntriesViewModel.kt b/app/src/main/java/com/rtbishop/look4sat/ui/entriesScreen/EntriesViewModel.kt index 151987d4..31d44cc9 100644 --- a/app/src/main/java/com/rtbishop/look4sat/ui/entriesScreen/EntriesViewModel.kt +++ b/app/src/main/java/com/rtbishop/look4sat/ui/entriesScreen/EntriesViewModel.kt @@ -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, isSelected: Boolean) { - viewModelScope.launch { - satelliteRepo.updateEntriesSelection(items, isSelected) - } + suspend fun updateEntriesSelection(items: List, isSelected: Boolean) { + satelliteRepo.updateEntriesSelection(items, isSelected) } } diff --git a/app/src/main/res/layout/fragment_entries.xml b/app/src/main/res/layout/fragment_entries.xml index 0e38dd08..3a2da3ca 100644 --- a/app/src/main/res/layout/fragment_entries.xml +++ b/app/src/main/res/layout/fragment_entries.xml @@ -51,15 +51,13 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> - @@ -118,5 +116,14 @@ + +