From 208c587ddf139f56855467dd68eef25066afc490 Mon Sep 17 00:00:00 2001 From: Arty Bishop Date: Mon, 27 Mar 2023 14:42:53 +0100 Subject: [PATCH] Created SelectionRepository, cleaned up code --- .../com/rtbishop/look4sat/MainContainer.kt | 5 + .../look4sat/framework/data/dao/EntriesDao.kt | 6 +- .../framework/data/local/EntriesStorage.kt | 6 +- .../look4sat/framework/model/SatItem.kt | 18 ++-- .../presentation/entries/EntriesScreen.kt | 44 ++++---- .../entries/EntriesScreenState.kt | 10 ++ .../presentation/entries/EntriesViewModel.kt | 102 ++++-------------- .../rtbishop/look4sat/data/DataRepository.kt | 12 +-- .../rtbishop/look4sat/data/IEntriesStorage.kt | 2 +- .../look4sat/data/SelectionRepository.kt | 73 +++++++++++++ .../look4sat/domain/IDataRepository.kt | 4 +- .../com/rtbishop/look4sat/model/SatItem.kt | 4 +- 12 files changed, 156 insertions(+), 130 deletions(-) create mode 100644 app/src/main/java/com/rtbishop/look4sat/presentation/entries/EntriesScreenState.kt create mode 100644 base/src/main/java/com/rtbishop/look4sat/data/SelectionRepository.kt diff --git a/app/src/main/java/com/rtbishop/look4sat/MainContainer.kt b/app/src/main/java/com/rtbishop/look4sat/MainContainer.kt index 3031c9a8..f7e83935 100644 --- a/app/src/main/java/com/rtbishop/look4sat/MainContainer.kt +++ b/app/src/main/java/com/rtbishop/look4sat/MainContainer.kt @@ -9,6 +9,7 @@ import androidx.room.Room import com.rtbishop.look4sat.data.DataParser import com.rtbishop.look4sat.data.DataRepository import com.rtbishop.look4sat.data.SatelliteRepository +import com.rtbishop.look4sat.data.SelectionRepository import com.rtbishop.look4sat.domain.IDataRepository import com.rtbishop.look4sat.domain.ISatelliteRepository import com.rtbishop.look4sat.domain.ISensorRepository @@ -58,6 +59,10 @@ class MainContainer(private val context: Context) { return DataRepository(parser, fileSource, entries, radios, remoteSource, scope, settings) } + fun provideSelectionRepository(): SelectionRepository { + return SelectionRepository(Dispatchers.Default, dataRepository, settingsRepository) + } + private fun provideSensorRepository(): ISensorRepository { val manager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager val sensor = manager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR) diff --git a/app/src/main/java/com/rtbishop/look4sat/framework/data/dao/EntriesDao.kt b/app/src/main/java/com/rtbishop/look4sat/framework/data/dao/EntriesDao.kt index ef4d972e..d320f5de 100644 --- a/app/src/main/java/com/rtbishop/look4sat/framework/data/dao/EntriesDao.kt +++ b/app/src/main/java/com/rtbishop/look4sat/framework/data/dao/EntriesDao.kt @@ -28,9 +28,9 @@ interface EntriesDao { @Query("SELECT COUNT(*) FROM entries") fun getEntriesTotal(): Flow - @Transaction - @Query("SELECT catnum, name FROM entries ORDER BY name ASC") - suspend fun getEntriesWithModes(): List +// @Transaction +@Query("SELECT catnum, name FROM entries ORDER BY name ASC") +suspend fun getEntriesList(): List @Transaction @Query("SELECT * FROM entries WHERE catnum IN (:selectedIds)") diff --git a/app/src/main/java/com/rtbishop/look4sat/framework/data/local/EntriesStorage.kt b/app/src/main/java/com/rtbishop/look4sat/framework/data/local/EntriesStorage.kt index 68ba01f2..12fc726d 100644 --- a/app/src/main/java/com/rtbishop/look4sat/framework/data/local/EntriesStorage.kt +++ b/app/src/main/java/com/rtbishop/look4sat/framework/data/local/EntriesStorage.kt @@ -29,8 +29,8 @@ class EntriesStorage(private val entriesDao: EntriesDao) : IEntriesStorage { override fun getEntriesTotal() = entriesDao.getEntriesTotal() - override suspend fun getEntriesWithModes(): List { - return entriesDao.getEntriesWithModes().toDomainItems() + override suspend fun getEntriesList(): List { + return entriesDao.getEntriesList().toDomainItems() } override suspend fun getEntriesWithIds(ids: List): List { @@ -50,7 +50,7 @@ class EntriesStorage(private val entriesDao: EntriesDao) : IEntriesStorage { private fun DomainEntry.toFramework() = FrameworkEntry(this.data, this.comment) - private fun FrameworkItem.toDomain() = DomainItem(this.catnum, this.name, this.modes, false) + private fun FrameworkItem.toDomain() = DomainItem(this.catnum, this.name, false) private fun List.toFrameworkEntries() = this.map { entry -> entry.toFramework() } diff --git a/app/src/main/java/com/rtbishop/look4sat/framework/model/SatItem.kt b/app/src/main/java/com/rtbishop/look4sat/framework/model/SatItem.kt index 43c6bfae..70f377f6 100644 --- a/app/src/main/java/com/rtbishop/look4sat/framework/model/SatItem.kt +++ b/app/src/main/java/com/rtbishop/look4sat/framework/model/SatItem.kt @@ -17,16 +17,14 @@ */ package com.rtbishop.look4sat.framework.model -import androidx.room.Relation - data class SatItem( val catnum: Int, - val name: String, - @Relation( - parentColumn = "catnum", - entity = SatRadio::class, - entityColumn = "catnum", - projection = ["mode"] - ) - val modes: List + val name: String +// @Relation( +// parentColumn = "catnum", +// entity = SatRadio::class, +// entityColumn = "catnum", +// projection = ["mode"] +// ) +// val modes: List ) diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/entries/EntriesScreen.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/entries/EntriesScreen.kt index cd63e47b..16ec5ae7 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/entries/EntriesScreen.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/entries/EntriesScreen.kt @@ -9,7 +9,6 @@ import androidx.compose.foundation.lazy.items import androidx.compose.foundation.text.BasicTextField import androidx.compose.material3.* import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment @@ -26,7 +25,6 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.viewmodel.compose.viewModel import com.rtbishop.look4sat.R -import com.rtbishop.look4sat.model.DataState import com.rtbishop.look4sat.model.SatItem import com.rtbishop.look4sat.presentation.CardIcon import com.rtbishop.look4sat.presentation.CardLoadingIndicator @@ -37,25 +35,33 @@ import com.rtbishop.look4sat.presentation.onClick @Composable fun EntriesScreen(navToPasses: () -> Unit) { val viewModel = viewModel(EntriesViewModel::class.java, factory = EntriesViewModel.Factory) - val state = viewModel.satData.collectAsState(initial = DataState.Loading) + val uiState = viewModel.uiState.value val showDialog = rememberSaveable { mutableStateOf(false) } val toggleDialog = { showDialog.value = showDialog.value.not() } if (showDialog.value) { - TypesDialog(list = viewModel.satTypes, selected = viewModel.getSatType(), toggleDialog) { - viewModel.setSatType(it) + TypesDialog(list = uiState.typesList, selected = uiState.currentType, toggleDialog) { + viewModel.setType(it) } } - val unselectAll = { viewModel.selectCurrentItems(false) } - val selectAll = { viewModel.selectCurrentItems(true) } + val unselectAll = { viewModel.updateSelection(false) } + val selectAll = { viewModel.updateSelection(true) } Column(modifier = Modifier.padding(6.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { TopBar(setQuery = { newQuery: String -> viewModel.setQuery(newQuery) }, saveSelection = { viewModel.saveSelection() navToPasses() }) - MiddleBar(viewModel.getSatType(), { toggleDialog() }, { unselectAll() }, { selectAll() }) - EntriesCard(state = state.value) { list, value -> viewModel.updateSelection(list, value) } + MiddleBar(uiState.currentType, { toggleDialog() }, { unselectAll() }, { selectAll() }) + ElevatedCard(modifier = Modifier.fillMaxSize()) { + if (uiState.isLoading) { + CardLoadingIndicator() + } else { + EntriesCard(uiState.itemsList) { list, value -> + viewModel.updateSelection(list, value) + } + } + } } } @@ -169,7 +175,7 @@ private fun EntryTypeCard(type: String, onClick: () -> Unit, modifier: Modifier @Preview(showBackground = true) @Composable private fun EntryPreview() { - val satItem = SatItem(45555, "SatName", emptyList(), true) + val satItem = SatItem(45555, "Very long Satellite name", true) MainTheme { Entry(item = satItem, onSelected = { _, _ -> run {} }, modifier = Modifier) } } @@ -206,23 +212,15 @@ private fun Entry(item: SatItem, onSelected: (List, Boolean) -> Unit, modif @Preview(showBackground = true) @Composable private fun EntriesPreview() { - MainTheme { EntriesCard(DataState.Loading) { _, _ -> run {} } } + MainTheme { EntriesCard(emptyList()) { _, _ -> run {} } } } @OptIn(ExperimentalFoundationApi::class) @Composable -fun EntriesCard(state: DataState>, onSelected: (List, Boolean) -> Unit) { - ElevatedCard(modifier = Modifier.fillMaxSize()) { - when (state) { - is DataState.Success -> { - LazyColumn { - items(items = state.data, key = { item -> item.catnum }) { entry -> - Entry(entry, onSelected, Modifier.animateItemPlacement()) - } - } - } - - else -> CardLoadingIndicator() +fun EntriesCard(items: List, onSelected: (List, Boolean) -> Unit) { + LazyColumn { + items(items = items, key = { item -> item.catnum }) { entry -> + Entry(entry, onSelected, Modifier.animateItemPlacement()) } } } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/entries/EntriesScreenState.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/entries/EntriesScreenState.kt new file mode 100644 index 00000000..2915ae54 --- /dev/null +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/entries/EntriesScreenState.kt @@ -0,0 +1,10 @@ +package com.rtbishop.look4sat.presentation.entries + +import com.rtbishop.look4sat.model.SatItem + +data class EntriesScreenState( + val isLoading: Boolean, + val itemsList: List, + val currentType: String, + val typesList: List, +) diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/entries/EntriesViewModel.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/entries/EntriesViewModel.kt index 4734b1ed..ba727b2b 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/entries/EntriesViewModel.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/entries/EntriesViewModel.kt @@ -17,115 +17,59 @@ */ package com.rtbishop.look4sat.presentation.entries +import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory import com.rtbishop.look4sat.MainApplication -import com.rtbishop.look4sat.domain.IDataRepository -import com.rtbishop.look4sat.domain.ISettingsRepository -import com.rtbishop.look4sat.model.DataState -import com.rtbishop.look4sat.model.SatItem -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi +import com.rtbishop.look4sat.data.SelectionRepository import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import java.util.* -@OptIn(ExperimentalCoroutinesApi::class) -class EntriesViewModel( - private val dataRepository: IDataRepository, - private val settingsRepository: ISettingsRepository, - private val dispatcher: CoroutineDispatcher = Dispatchers.Default -) : ViewModel() { +class EntriesViewModel(private val selectionRepo: SelectionRepository) : ViewModel() { - private val satType = MutableStateFlow("All") - private val currentQuery = MutableStateFlow(String()) - private val itemsFromRepo = MutableStateFlow>(emptyList()) - private val itemsWithType = satType.flatMapLatest { type -> - itemsFromRepo.map { items -> filterByType(items, type) } - } - private val itemsWithQuery = currentQuery.flatMapLatest { query -> - itemsWithType.map { items -> filterByQuery(items, query) } - } - val satData = itemsWithQuery.map { items -> DataState.Success(items) } - val satTypes = dataRepository.getSatelliteTypes() + private val defaultUiState = EntriesScreenState( + isLoading = true, + itemsList = emptyList(), + currentType = selectionRepo.getCurrentType(), + typesList = selectionRepo.getTypesList() + ) + val uiState = mutableStateOf(defaultUiState) init { viewModelScope.launch { - delay(250) - itemsFromRepo.value = dataRepository.getEntriesWithSelection() + delay(1000) + selectionRepo.getEntriesFlow().collect { items -> + uiState.value = uiState.value.copy(isLoading = false, itemsList = items) + } } } - fun getSatType() = satType.value - - fun setSatType(type: String) { - satType.value = type + fun setType(type: String) = viewModelScope.launch { + selectionRepo.setType(type) + uiState.value = uiState.value.copy(currentType = type) } - fun selectCurrentItems(selectAll: Boolean) = viewModelScope.launch { - updateEntriesSelection(itemsWithQuery.first().map { item -> item.catnum }, selectAll) - } + fun setQuery(query: String) = viewModelScope.launch { selectionRepo.setQuery(query) } - fun setQuery(query: String) { - currentQuery.value = query - } - - fun saveSelection() = viewModelScope.launch { - val newSelection = itemsFromRepo.value.filter { it.isSelected }.map { it.catnum } - settingsRepository.saveEntriesSelection(newSelection) + fun updateSelection(selectAll: Boolean) = viewModelScope.launch { + selectionRepo.updateSelection(selectAll) } fun updateSelection(catNums: List, isSelected: Boolean) = viewModelScope.launch { - updateEntriesSelection(catNums, isSelected) + selectionRepo.updateSelection(catNums, isSelected) } - private suspend fun updateEntriesSelection(catNums: List, isSelected: Boolean) { - withContext(dispatcher) { - itemsFromRepo.value.let { itemsAll -> - val copiedList = itemsAll.map { item -> item.copy() } - catNums.forEach { catnum -> - copiedList.find { item -> item.catnum == catnum }?.isSelected = isSelected - } - itemsFromRepo.value = copiedList - } - } - } - - private suspend fun filterByType(items: List, type: String): List { - return withContext(dispatcher) { - if (type == "All") return@withContext items - val catnums = settingsRepository.loadSatType(type) - if (catnums.isEmpty()) return@withContext items - return@withContext items.filter { item -> item.catnum in catnums } - } - } - - private suspend fun filterByQuery(items: List, query: String): List { - return withContext(dispatcher) { - if (query.isBlank()) return@withContext items - return@withContext try { - items.filter { it.catnum == query.toInt() } - } catch (e: Exception) { - items.filter { item -> item.name.lowercase().contains(query.lowercase()) } - } - } - } + fun saveSelection() = viewModelScope.launch { selectionRepo.saveSelection() } companion object { val Factory: ViewModelProvider.Factory = viewModelFactory { val applicationKey = ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY initializer { val container = (this[applicationKey] as MainApplication).container - EntriesViewModel( - container.dataRepository, - container.settingsRepository - ) + EntriesViewModel(container.provideSelectionRepository()) } } } diff --git a/base/src/main/java/com/rtbishop/look4sat/data/DataRepository.kt b/base/src/main/java/com/rtbishop/look4sat/data/DataRepository.kt index 709dfac5..5f5949e5 100644 --- a/base/src/main/java/com/rtbishop/look4sat/data/DataRepository.kt +++ b/base/src/main/java/com/rtbishop/look4sat/data/DataRepository.kt @@ -55,15 +55,13 @@ class DataRepository( override fun getSatelliteTypes() = remoteSource.satelliteSourcesMap.keys.sorted() - override suspend fun getEntriesWithModes() = entrySource.getEntriesWithModes() + override suspend fun getEntriesList(): List { + val selectedIds = settingsRepository.satelliteSelection.value + return entrySource.getEntriesList().map { it.copy(isSelected = it.catnum in selectedIds) } + } override suspend fun getEntriesWithIds(ids: List) = entrySource.getEntriesWithIds(ids) - override suspend fun getEntriesWithSelection(): List { - val selectedIds = settingsRepository.satelliteSelection.value - return getEntriesWithModes().onEach { it.isSelected = it.catnum in selectedIds } - } - override suspend fun getRadiosWithId(id: Int) = radioSource.getRadiosWithId(id) override fun updateFromFile(uri: String) { @@ -93,6 +91,7 @@ class DataRepository( settingsRepository.saveSatType(type, catnums) importedEntries.addAll(satellites) } + "McCants", "Classified" -> { // unzip and parse tle stream val unzipped = ZipInputStream(stream).apply { nextEntry } @@ -101,6 +100,7 @@ class DataRepository( settingsRepository.saveSatType(type, catnums) importedEntries.addAll(satellites) } + else -> { // parse csv stream val parsed = dataParser.parseCSVStream(stream) diff --git a/base/src/main/java/com/rtbishop/look4sat/data/IEntriesStorage.kt b/base/src/main/java/com/rtbishop/look4sat/data/IEntriesStorage.kt index 3d5f7fb0..f3c9f583 100644 --- a/base/src/main/java/com/rtbishop/look4sat/data/IEntriesStorage.kt +++ b/base/src/main/java/com/rtbishop/look4sat/data/IEntriesStorage.kt @@ -26,7 +26,7 @@ interface IEntriesStorage { fun getEntriesTotal(): Flow - suspend fun getEntriesWithModes(): List + suspend fun getEntriesList(): List suspend fun getEntriesWithIds(ids: List): List diff --git a/base/src/main/java/com/rtbishop/look4sat/data/SelectionRepository.kt b/base/src/main/java/com/rtbishop/look4sat/data/SelectionRepository.kt new file mode 100644 index 00000000..2a1c6df5 --- /dev/null +++ b/base/src/main/java/com/rtbishop/look4sat/data/SelectionRepository.kt @@ -0,0 +1,73 @@ +package com.rtbishop.look4sat.data + +import com.rtbishop.look4sat.domain.IDataRepository +import com.rtbishop.look4sat.domain.ISettingsRepository +import com.rtbishop.look4sat.model.SatItem +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.withContext + +@OptIn(ExperimentalCoroutinesApi::class) +class SelectionRepository( + private val dispatcher: CoroutineDispatcher, + private val dataRepository: IDataRepository, + private val settingsRepository: ISettingsRepository +) { + private val currentItems = MutableStateFlow>(emptyList()) + private val currentType = MutableStateFlow("All") + private val currentQuery = MutableStateFlow("") + private val itemsWithType = currentType.flatMapLatest { type -> + currentItems.map { items -> items.filterByType(type) } + } + private val itemsWithQuery = currentQuery.flatMapLatest { query -> + itemsWithType.map { items -> items.filterByQuery(query) } + } + + fun getCurrentType() = currentType.value + + fun getTypesList() = dataRepository.getSatelliteTypes() + + suspend fun getEntriesFlow() = withContext(dispatcher) { + currentItems.value = dataRepository.getEntriesList() + return@withContext itemsWithQuery + } + + suspend fun setType(type: String) = withContext(dispatcher) { currentType.value = type } + + suspend fun setQuery(query: String) = withContext(dispatcher) { currentQuery.value = query } + + suspend fun updateSelection(selectAll: Boolean) = withContext(dispatcher) { + updateSelection(itemsWithQuery.first().map { item -> item.catnum }, selectAll) + } + + suspend fun updateSelection(catNums: List, isSelected: Boolean) = withContext(dispatcher) { + currentItems.value = currentItems.value.map { item -> + if (item.catnum in catNums) item.copy(isSelected = isSelected) else item + } + } + + suspend fun saveSelection() = withContext(dispatcher) { + val currentSelection = currentItems.value.filter { it.isSelected }.map { it.catnum } + settingsRepository.saveEntriesSelection(currentSelection) + } + + private suspend fun List.filterByType(type: String) = withContext(dispatcher) { + if (type == "All") return@withContext this@filterByType + val catnums = settingsRepository.loadSatType(type) + if (catnums.isEmpty()) return@withContext this@filterByType + return@withContext this@filterByType.filter { item -> item.catnum in catnums } + } + + private suspend fun List.filterByQuery(query: String) = withContext(dispatcher) { + if (query.isBlank()) return@withContext this@filterByQuery + return@withContext try { + this@filterByQuery.filter { it.catnum == query.toInt() } + } catch (e: Exception) { + this@filterByQuery.filter { item -> item.name.lowercase().contains(query.lowercase()) } + } + } +} diff --git a/base/src/main/java/com/rtbishop/look4sat/domain/IDataRepository.kt b/base/src/main/java/com/rtbishop/look4sat/domain/IDataRepository.kt index 520a5ce9..7c1ccd5b 100644 --- a/base/src/main/java/com/rtbishop/look4sat/domain/IDataRepository.kt +++ b/base/src/main/java/com/rtbishop/look4sat/domain/IDataRepository.kt @@ -33,12 +33,10 @@ interface IDataRepository { fun getSatelliteTypes(): List - suspend fun getEntriesWithModes(): List + suspend fun getEntriesList(): List suspend fun getEntriesWithIds(ids: List): List - suspend fun getEntriesWithSelection(): List - suspend fun getRadiosWithId(id: Int): List fun updateFromFile(uri: String) diff --git a/base/src/main/java/com/rtbishop/look4sat/model/SatItem.kt b/base/src/main/java/com/rtbishop/look4sat/model/SatItem.kt index 4c68f3fa..1c163b97 100644 --- a/base/src/main/java/com/rtbishop/look4sat/model/SatItem.kt +++ b/base/src/main/java/com/rtbishop/look4sat/model/SatItem.kt @@ -20,6 +20,6 @@ package com.rtbishop.look4sat.model data class SatItem( val catnum: Int, val name: String, - val modes: List, - var isSelected: Boolean +// val modes: List, + val isSelected: Boolean )