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 146c761e..77663023 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 @@ -29,13 +29,13 @@ interface SatelliteDao { // Query - @Transaction - @Query("SELECT catNum, name, isSelected FROM entries ORDER BY name ASC") - fun getAllSatItems(): Flow> - @Query("SELECT * FROM transmitters WHERE catNum = :catNum") fun getTransmittersByCatNum(catNum: Int): Flow> + @Transaction + @Query("SELECT catNum, name, isSelected FROM entries ORDER BY name ASC") + suspend fun getAllSatItems(): List + @Query("SELECT tle FROM entries WHERE isSelected = 1") suspend fun getSelectedSatellites(): List @@ -59,13 +59,20 @@ interface SatelliteDao { // Update @Transaction - suspend fun updateEntriesSelection(catNums: List, isSelected: Boolean) { + suspend fun restoreEntriesSelection(catNums: List, isSelected: Boolean) { clearEntriesSelection() catNums.forEach { catNum -> updateItemSelection(catNum, isSelected) } } + @Transaction + suspend fun updateEntriesSelection(catNums: List, 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() } 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 3f66d3ec..dc4074e8 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 @@ -48,14 +48,14 @@ class SatelliteRepo @Inject constructor( // Query - fun getAllSatItems(): Flow> { - return satelliteDao.getAllSatItems() - } - fun getSatTransmitters(catNum: Int): Flow> { return satelliteDao.getTransmittersByCatNum(catNum) } + suspend fun getAllSatItems(): List { + return satelliteDao.getAllSatItems() + } + suspend fun getSelectedSatellites(): List { return satelliteDao.getSelectedSatellites() } @@ -131,7 +131,7 @@ class SatelliteRepo @Inject constructor( private suspend fun insertEntriesAndRestoreSelection(entries: List) { val selectedCatNums = satelliteDao.getSelectedCatNums() satelliteDao.insertEntries(entries) - satelliteDao.updateEntriesSelection(selectedCatNums, true) + satelliteDao.restoreEntriesSelection(selectedCatNums, true) } // Update 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 6ff3ad14..f6cfb380 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 @@ -40,9 +40,14 @@ class EntriesAdapter : RecyclerView.Adapter() { private val listDiffer = AsyncListDiffer(this, diffCallback) private val allItems = mutableListOf() private var shouldSelectAll = true + private lateinit var entriesClickListener: EntriesClickListener - fun getSelectedIds(): List { - return allItems.filter { it.isSelected }.map { it.catNum } + interface EntriesClickListener { + fun updateSelection(catNums: List, isSelected: Boolean) + } + + fun setEntriesClickListener(listener: EntriesClickListener) { + entriesClickListener = listener } fun submitAllItems(items: List) { @@ -58,6 +63,7 @@ class EntriesAdapter : RecyclerView.Adapter() { newList.add(item) } submitCurrentItems(newList) + entriesClickListener.updateSelection(newList.map { it.catNum }, shouldSelectAll) shouldSelectAll = !shouldSelectAll } @@ -98,16 +104,19 @@ class EntriesAdapter : RecyclerView.Adapter() { } 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 { 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 7311a038..2b83410d 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 @@ -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) { 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 } 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 31d44cc9..0a96e380 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 @@ -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() + private var allSatItems = listOf() private val satDataState = MutableStateFlow>>(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 { - return prefsRepo.loadModesSelection() - } - - fun filterByModes(modes: List) { + private fun filterByModes(modes: List) { 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, 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, isSelected: Boolean) { + viewModelScope.launch { + satelliteRepo.updateEntriesSelection(catNums, isSelected) + } } } diff --git a/app/src/main/res/layout/fragment_entries.xml b/app/src/main/res/layout/fragment_entries.xml index 3a2da3ca..d138acc6 100644 --- a/app/src/main/res/layout/fragment_entries.xml +++ b/app/src/main/res/layout/fragment_entries.xml @@ -103,7 +103,7 @@ app:layout_constraintTop_toBottomOf="@+id/entries_bar" /> - -